A better word for Aggregate Root

1,816 views
Skip to first unread message

Herman Peeren

unread,
Sep 1, 2016, 9:19:44 AM9/1/16
to DDD/CQRS

The core activity of DDD is building a model, in a team together with developers and domain experts. Good communication between those two groups is essential. Modeling implies defining a common vocabulary. A very specific DDD jargon is not very helpful in that communication and should be avoided. Words like “ubiqitous language”, “bounded context” or “aggregate root” are not inviting non-insiders to share their domain knowledge; I've seen people being scared off by the use of those strange words. In part 1 of this series I investigated the concept of "bounded context". In this second part I propose an alternative for “aggregate root (AR)”.


In the DDD literature the words “aggregate” and “aggregate root” (often abbreviated to AR) are used very frequently. They are core concepts. For developers they sound familiar and are the logical consequence of working with groups of objects, respecting the Law of Demeter and Tell, Don't Ask, while guarding invariants. We have used those terms for decades, from the times we mainly worked with ERDs and ORMs. But for non-technical team members those words are strange jargon.


Learned from DCI

In DCI (Data-Context-Interaction, see http://fulloo.info/Documents/) the behaviour of objects is specific within the context of a use case: the objects are said to play a “role” in that context. Just like a person has different behaviour playing different “roles” in daily life (for instance as a developer, as a customer, as a mother, as a lover). In a DCI context all the behaviour is gathered that is needed to enact a use case. The roles form invariants in the use case. A context in DCI is comparable to an aggregate in “our” DDD. The behaviour of the roles is comparable to the interface of the aggregate root. Of course there are differences, for in class-oriented programming you cannot easily add behaviour to objects at runtime. But broadly speaking, looking at the similarities instead of at the differences, a DCI context is comparable to a DDD aggregate. An interesting aspect is that the origin of an aggregate is structural (some coherent objects), coming from a data-centric era of development, while a DCI context is mainly based on behavioural coherence, like the concept of invariants is.


One of the good things of DCI contexts is that they correspond one-on-one to use cases. In the 2010 book “Lean Architecture for Agile Software Development”James Coplien and Gertrud Bjørnvig show how well use cases support Agile development. They are good tools for incremental delivery of business value. And they are also familiar concepts for the persons with domain knowledge: we can easily communicate about a succes scenario and alternative scenarios that form a full blown use case. And the focus is immediately where it should be: on the behaviour.


That's why I prefer “use case” above “agregate root”.


Use Case

It is nothing new that an aggregate root as used in DDD corresponds to a use case. Here are for instance some quotes from Scott Millett's book “Patterns, Principles and Practices of Domain-Driven Design” (2015): “When defining aggregates, you need to identify objects that work together and must be consistent with each other to fulfill business use cases” (page 444), “strive to modify a single aggregate per use case” (page 446), “justify each grouping and ensure that each object is required to define the behaviour of the aggregate instead of just being related to the aggregate”(page 449) and “Focus on modeling aggregates from the perspective of your business use cases” (page 449). However, in “Implementing Domain-Driven Design” (2013) Vaughn Vernon warns us for blindly trusting every use case (page 358-359), but apparently he sees use cases mostly as the product of business analysts, where I see the use cases as emergent from the modeling process, by the joint efforts of the team of developers and domain experts. So be careful with the word “use case” as it has a slightly different meaning for different people. For me a use case is the space between some blue and orange stickies of commands and events in a model storming session; what usually is called an aggregate.


I have a slightly different naming convention for use cases in comparison with aggregate roots, reflecting its behavioural nature: I often use the present participle. It is the place where things happen. The name also mostly fits with the name of commands and events. I get a sequence of command → use case → event: register → registering → registered, pay → paying → payed, etc. (mostly more verbose, including the name of the main object in the name of the command, use case and event, like registerMember etc., but you get the idea). This naming convention shows what is being done in a use case as opposed to the more static name of an object when naming an aggregate root. A use case is more about what the system does (behaviour) than what the system is (structure).


Domain object in multiple use cases

So in my models I treat use cases as first class citizens. Technically seen the use case still is the root of an aggregate, containing all objects involved in the use case. Its methods are not part of one of the domain objects in the aggregate, but of the use case itself. Unfortunately in class-oriented programming we cannot easily add methods to objects at run time, like in “pure” DCI. But we do have all methods belonging to the use case together in one (use case) object. That prevents adding methods to classes for objects that are used in multiple use cases. For instance if a Member would be an object in a registeringMember use case, and that member object would also be used in another use case in the same model (in DDD jargon: within the same Bounded Context) then we can now use that member object in the other use case without the registering methods. The registering methods are only applicable to the member object within the registering use case. I'm aware that this is opposite to the goal of giving domain objects “rich behaviour”, but it has the advantage of having all use case specific behaviour together in one place. It is more in the direction of functional programming (composing behaviour) than object oriented programming (emphasising structure, like relations between objects).


In his 1965 essay “A City is Not a Tree” Christopher Alexander shows the “real world” is too complex to divide it in mutually exclusive concepts. There is a lot of overlap and interference. In DDD we experience that when using multiple models (in DDD jargon: multiple Bounded Contexts) that use the same word for a concept in a different context. For instance a product in the context of a sales model, an inventory model, a fulfillment model etc. Within one model we have a consistent meaning for the name of a domain object, but it can still be used in different use cases, each with different invariants and behaviour. Explicitly naming the use case as such takes away the problem that object behaviour from another use case is part of some aggregate root. I'm still looking for concrete cases, but I guess that in current DDD practice such cases are sometimes solved by adding an extra Bounded Context (although the meaning of the used domain objects is in fact the same and so they should belong to the same model).


Summary

I don't have to use technical terms like “aggregate” and “aggregate root” in modeling sessions. We model use cases, consisting of scenarios handling success, failure or edge cases, being invoked by commands and resulting in events. Clear and immediately understandable, also for non-insiders of the DDD jargon.



pagoda_5b

unread,
Sep 1, 2016, 7:40:17 PM9/1/16
to DDD/CQRS
Hello
First of all thanks for the effort of re-examine the foundations of DDD to see what's still relevant and meaningful after 10+ yrs of history.

I have two main points to make here.

1. Relating to the weirdness of the jargon for non-techies.
Do you assume here that DDD idioms like "aggregate", "aggregate root", "value-object", "entity" etc. are part of the Ubiquitous Language (or Model Language, Context Language whatsoever) ?
I don't expect a business analyst or marketing people to understand, or even talk about, aggregate roots. It's technical jargon used at the tactical level, hence an implementation detail reserved to the development team.
I don't find it necessary (but neither prohibited) to explain those terms to the non-technical people. I assume we share the domain-model language, but nothing more.
It would be like expecting that the marketing or product manager need to understand the details of CQRS, or AMQP, or a Clustered NoSql solution, to name a few.

2. Whereas there's a tight connection between AR and the use-case, the historical role of the AR has mainly been that of containing the data (actually accessing all the necessary data and nothing more) for a single transaction. Also it's considered the entry point for other actors in the system to access the hidden layers of the aggregate itself.
In traditional oop-based DDD, I'd say that the behavioural part (which is more relevant in the DCI approach) is actually some syntactic sugar to consistently and conveniently enclose a set of data-manipulations within a single business-relevant operation, with a meaningful name on top.
This is why I think that the name aggregate is still meaningful, as long as you treat it as a data structure with useful operations to modify it stacked in the root object.
With a DCI approach, it sounds to me that you should detach yourself from the historical oo-centric idea of tying behaviours and data.
Taking this further on, aggregates could remain as data-structures while behaviours would move to somewhere else.

I'm still not familiar with DCI, but to me it resonates with the functional programming idea of typeclasses, as widely popularized by haskell. There you have a neat distinction between operations (corresponding to the use-cases) and the data. The roles would correspond to a data-type being declared instance of a typeclass.
In event-sourced architectures this would be more relevant still, because the system current state is nothing more but the accumulating result of events (which is immutable data).

What's unclear to me at the moment in DCI is what behaviour should be tied to the Data part and what to the Context part.

Ivano

Thomas Schanko

unread,
Sep 2, 2016, 8:46:22 PM9/2/16
to DDD/CQRS
I've been playing around with the idea of combining DDD and DCI for a while now, and I still belive that it is beneficial for both sides. However, I prefer a different approach. To me, an aggregate root in DDD corresponds to an object in DCI. Roles and Use Cases go on top of that. Also my way of doing it is not 'Cope-Compliant", it follows the ideas of the original DCI Vision.

Rinat Abdullin

unread,
Sep 11, 2016, 8:28:19 AM9/11/16
to DDD/CQRS
Herman,

Thank you for a really thoughtful article. It was a very interesting read.

Your approach aligns well with my experience in DDD/EDD in the last years (although, I'm still struggling with the words). Here's the terminology that we are currently using (across business and developers):

- Use case ("As a user I want to be able...") - desired behaviour of the system that is captured by the domain experts and analysts and then entered in JIRA.
- Scenarios ("Given X events, When HTTP POST "/picking/list/{id}/complete", Then Y events, Then HTTP OK { Json }") - human-written specifications that capture different edge cases of the desired behavior (e.g. errors, happy path, etc). They use API and event contracts to do that (and hence are decoupled from implementation(s)). Scenarios are linked to the Use-Cases in JIRA and are used to verify and maintain correctness of the system.

From this perspective, "aggregates" represent code that implements a couple of use-cases, where each use case can be detailed by one or more scenarios. Term "aggregate" never surfaces on the discussions (since it is an artifact of development in our case) and I feel that we are using it less and less.

Best regards,
Rinat

Greg Young

unread,
Sep 11, 2016, 12:20:37 PM9/11/16
to ddd...@googlegroups.com
I don't use the words aggregate or aggregate root when talking about a
model. They are a technical pattern that you will see implemented in
the model but they are not usually discussed with say business people.

When modeling it is common to talk about use cases and results
(command and event) as you point out. Another point here would be to
mention that application services / command handlers map to a use case
in other 'styles' of building domain models.

For non-trivial use cases there may however be quite a bit between the
command and the event. Other concepts involved in the process. A good
example of this would be PlaceOrder -> TradeOccurred in a stock
market. There are lots of concepts in between such as OrderBooks,
Orders (varying types), etc likely implemented as part of an
Instrument aggregate.

You can't just say "I will use the term use case instead of
aggregate". They are quite different things. It makes roughly the same
amount of sense as saying I will call a use case a document (as in
with a document store). That said the aggregate pattern is really
doing multiple things at the same time (its defining storage level
consistency assumptions as well as hosting behaviour).

To be fair I rarely use aggregates at all any longer. I tend to just
go handler -> events and to do it in a functional style.

Cheers,

Greg
> --
> You received this message because you are subscribed to the Google Groups
> "DDD/CQRS" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to dddcqrs+u...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.



--
Studying for the Turing test

Herman Peeren

unread,
Sep 12, 2016, 3:20:19 AM9/12/16
to DDD/CQRS
"Aggregate" is a term from the OO-paradigm.

When using pure functional programming, state and data don't exist other than being the result of a left fold. So you have no objects and hence no entities, value objects or aggregates. Event Sourcing is based on this functional paradigm: we don't store state but only events that caused a state change. State, objects, etcetera can be produced in projections to be used in the read-side. If ES is used purely functional there is hardly any need for objects in the write-side model; a logical consequence is that aggregates will rarely be used then.

Freek Paans

unread,
Sep 12, 2016, 3:58:35 AM9/12/16
to ddd...@googlegroups.com
Greg, do you use something else to reason about transactional consistency when you aren't using aggregates?

This thread reminds me of this section from http://www.artima.com/articles/dci_vision.html, which I guess is (part of) the motiviation for DCI:

"Unfortunately, object boundaries already mean something else: they are loci of encapsulated domain knowledge, of the data. There is little that suggests that the stepwise refinement of an algorithm into cognitive chunks should match the demarcations set by the data model. Old-fashioned object orientation forced us to use the same mechanism for both demarcations, and this mechanism was called a class. One or the other of the demarcating mechanisms is likely to win out. If the algorithmic decomposition wins out, we end up with algorithmic fragments landing in one object but needing to talk to another, and coupling metrics suffer. If the data decomposition wins out, we end up slicing out just those parts of the algorithm that are pertinent to the topic of the object to which they are assigned, and we end up with very small incohesive methods. Old-fashioned object orientation explicitly encouraged the creation of such fine-grain methods, for example, a typical Smalltalk method is three statements long."

The OP's article favors towards keeping the algorithm together, while maybe the original definition leans more toward the data decomposition. Guess there's merit in both views, not sure if we should redefine aggregate though.

Cheers,

-Freek


> For more options, visit https://groups.google.com/d/optout.



--
Studying for the Turing test
--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+unsubscribe@googlegroups.com.

Herman Peeren

unread,
Sep 12, 2016, 4:00:39 AM9/12/16
to DDD/CQRS
On Sunday, 11 September 2016 14:28:19 UTC+2, Rinat Abdullin wrote:
From this perspective, "aggregates" represent code that implements a couple of use-cases, where each use case can be detailed by one or more scenarios.

When I have multiple use cases using the same domain objects in one model, I implement them in different aggregates by giving them a different aggregate root (named after the use case and with the behaviour of that uses case as interface). But it also alerts me to look if the use cases are really distinct, or maybe just different aspects of the same use case. Theoretically it can be proved that two use cases with the same domain objects can be joined (using one or more parameters to distinguish between the two use cases). After inspection we have to decide whether it is useful and more clear to keep the use cases split or to join them in a more general use case. I have some concrete examples of this process; will show it another time.

Modeling (and programming) is an "art", by which I mean that we often have to take decisions that are based on a feeling of what is better. One of the most important decisions is how to divide things: do we split a model in multiple models, do we split an aggregate in multiple aggregates, do we split a class in multiple classes, do we put methods in this class or in that one? Where do we split? Of course there are "rules" underlying those decisions: consistency boundaries, invariants, SRP, etc. Studying those rules develops our feeling of what is better and what is worse. Some decisions how to divide are obvious and easy, but often in the process we can take multiple roads, each with advantages and disadvantages. Often those approaches are all valid and it is hard to say which solution is better; in those cases we trust our (developed) feeling of what is the way to go.

Daniel Plainview

unread,
Sep 12, 2016, 5:53:18 AM9/12/16
to DDD/CQRS
> When I have multiple use cases using the same domain objects in one model, I implement them in different aggregates by giving them a different aggregate root (named after the use case and with the behaviour of that uses case as interface)

It looks like you have different meaning of the "use case" and "aggregate".
If you say that you have aggregate per use case, it means that you create 2 different classes for these 2 actions (aka use cases):

$user->block($until, $reason)
$user->unblock()

BlockUser and UnblockUser are use cases in the User aggregate and I don't see why they have to be in different aggregates.
You can't just replace "aggregate" with "use case", because they are different concepts.

Herman Peeren

unread,
Sep 12, 2016, 7:38:19 AM9/12/16
to DDD/CQRS
I didn't say they have to be in different aggregates, but only that they can be (and that that is, for me personally, my starting point in the modeling process when using an OO paradigm: implement use cases with aggregates). The rationale behind it is to put emphasis on the behavioural aspects instead of the structural aspects of an aggregate (as is the more general use of an aggregate as a coherent set of objects, an object graph). In DDD an aggregate is only the (minimal) set of objects needed to guard an invariant.That doesn't have to include the whole object graph, but can be a subset. You can (not: you must) model those two use cases as one, for instance as a block-command with a start/stop-parameter or you can split them into two separate commands as you did in your example: block and unblock. Those two use cases have two different (probably mutually exclusive) sets of invariants and putting them in two separate aggregates (in this case including the same user domain object) encapsulates those two different policies.

A user in my model is not an aggregate "in itself" (as a kind of "promoted" object), but an entity. It is only placed into an aggregate when fulfilling a use case. If you do it the other way around, as is often done, you run more risk to be stuck in structural, data centric thinking instead of emphasising behaviour and invariants.That was the starting point of my discussion: I want to share my process of which I think it might lead to better models, by going from use cases to implementation (using aggregates for that implementation when in the OO paradigm) instead of the other way around, defining all kinds of "aggregates" and then putting some behaviour in that to fulfill the use cases.

Herman Peeren

unread,
Sep 12, 2016, 8:19:41 AM9/12/16
to DDD/CQRS
A difference of what I mostly do when using an aggregate in camparison to how it is usually done in DDD: I make a separate object as aggregate root, that is not one of the constituting domain objects. In DDD-jargon such an object is a Service (in GRASP patterns: Pure Fabrication), not an Entity. Rationale behind it is that the behaviour in that aggregate and its interface is specific for that aggregate and hence should not just be added to the constituting domain objects: that behaviour should not be available outside the use case.

Greg Young

unread,
Sep 12, 2016, 8:26:51 AM9/12/16
to ddd...@googlegroups.com
In reading this I am unable to understand what your definition of
aggregate or aggregate root are but they do not seem to be the same
definitions that I have. Perhaps you could put up a code example to be
more concrete?
> --
> You received this message because you are subscribed to the Google Groups
> "DDD/CQRS" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to dddcqrs+u...@googlegroups.com.

Daniel Plainview

unread,
Sep 12, 2016, 8:28:43 AM9/12/16
to DDD/CQRS
How does it differ from command handlers then?

Kirill Chilingarashvili

unread,
Sep 12, 2016, 2:35:42 PM9/12/16
to DDD/CQRS
I see an aggregate as a guard in front of domain layer.
A guard - using "face-control" or other invariant checking logic decide, whether to allow new events into the club (domain logic) or not, whether to allow the events to happen or no.
I somehow agree that aggregates in ES context look differently than in old times. 
Today - for me an aggregate is just a guard, validator. 
An aggregate today is synchronous guard able to say NO synchronously to caller trying to make modification to a domain.
For me an aggregate is very short lived single transaction pre-validator.
Last month I was trying to rethink, whether it is worth to write aggregate by hand at all.
If you think about it - an aggregate can be fully described declaratively,
There are no possible situations known to me which cannot be described declaratively.
All wire ups to infrastructure and other parts not manageable by single transaction the aggregate uses for storing events (and state for non-ES aggregates), are done by sagas / other workflows

I was able to declare and run aggregates in dynamically describable schema, and it just works.

Here is a sample of a schema, it still lacks validation part

But what I wanted to say - for me an aggregate is just validator around some invariants, and invariants can and will in many case span many use cases


{
 
"name": "Test",
 
"version": 1,
 
"commands": [
   
{
     
"name": "CommandName",
     
"version": 1,
     
"assertions": [
       
{
         
"type": "isNew",
         
"parameters": ""
       
}
     
],
     
"events": [
       
{
         
"name": "Event1",
         
"mappings": [
           
{
             
"target": "TestString",
             
"expression": "command.TestString"
           
},
           
{
             
"target": "TestText",
             
"expression": "command.TestText"
           
}
         
]
       
},
       
{
         
"name": "Event2",
         
"mappings": [
           
{
             
"target": "TestBoolean",
             
"expression": "command.TestBoolean"
           
},
           
{
             
"target": "TestString",
             
"expression": "command.TestString"
           
}
         
]
       
}
     
],
     
"properties": [
       
{
         
"name": "TestString",
         
"type": "string",
         
"length": "_100"
       
},
       
{
         
"name": "TestText",
         
"type": "text",
         
"length": "_100"
       
},
       
{
         
"name": "TestBoolean",
         
"type": "boolean",
         
"length": "_100"
       
}
     
]
   
}
 
],
 
"events": [
   
{
     
"name": "Event1",
     
"version": 1,
     
"mappings": [
       
{
         
"target": "TestString",
         
"expression": "event.TestString"
       
},
       
{
         
"target": "TestText",
         
"expression": "event.TestText"
       
}
     
],
     
"properties": [
       
{
         
"name": "TestString",
         
"type": "string",
         
"length": "_100"
       
},
       
{
         
"name": "TestText",
         
"type": "text",
         
"length": "_100"
       
}
     
]
   
},
   
{
     
"name": "Event2",
     
"version": 1,
     
"mappings": [
       
{
         
"target": "TestBoolean",
         
"expression": "event.TestBoolean"
       
},
       
{
         
"target": "TestString",
         
"expression": "event.TestString"
       
}
     
],
     
"properties": [
       
{
         
"name": "TestBoolean",
         
"type": "boolean",
         
"length": "_100"
       
},
       
{
         
"name": "TestString",
         
"type": "string",
         
"length": "_100"
       
}
     
]
   
}
 
],
 
"view": {
   
"version": 1,
   
"indexes": [
     
{
       
"properties": "TestString,TestInteger"
     
},
     
{
       
"properties": "TestBoolean,TestDateTime,TestString"
     
}
   
],
   
"properties": [
     
{
       
"name": "TestString",
       
"type": "string",
       
"length": "_100"
     
},
     
{
       
"name": "TestBoolean",
       
"type": "boolean",
       
"length": "_100"
     
},
     
{
       
"name": "TestText",
       
"type": "text",
       
"length": "_100"
     
},
     
{
       
"name": "TestInteger",
       
"type": "integer",
       
"length": "_100"
     
},
     
{
       
"name": "TestLong",
       
"type": "long",
       
"length": "_100"
     
},
     
{
       
"name": "TestDouble",
       
"type": "double",
       
"length": "_100"
     
},
     
{
       
"name": "TestDateTime",
       
"type": "dateTime",
       
"length": "_100"
     
},
     
{
       
"name": "TestGuid",
       
"type": "guid",
       
"length": "_100"
     
}
   
]
 
}
}




Thomas Schanko

unread,
Sep 12, 2016, 4:27:32 PM9/12/16
to DDD/CQRS
I agree with Greg. Reasoning about coding style without code is strange.

Herman Peeren

unread,
Sep 13, 2016, 5:49:36 AM9/13/16
to DDD/CQRS

Example domain: planning of crew members for hot air balloons.


A hot air balloon company in the Netherlands has several balloons. Flights are planned by attaching a balloon_id (“call sign”) to a datepart (= date + evening or morning) and a departure place. Every balloon has a fixed list of tasks to which crew members have to be assigned. Tasks are the pilot and the other crew members that drive the support cars. Tasks need skills, for instance a pilot licence of a minimum degree required for the balloon or a driving licence with or without possibility to drive a trailer. Crew members have skills and can online indicate their availability. Flights are planned from a year in advance. Departure place can be changed, for instance due to changed wind directions. A lot of planned flights are canceled due to the weather conditions (rain or too much wind). Passengers of a canceled flight have to make a new reservation for another flight (but that is another model).


The crew members indicate their availability. The planner plans the flights. The system assigns crew members to tasks, based on their availability and the needed skills (and some other preferences). The planner can change an assignment and confirm it. The crew members can then confirm their assignments.


In the use case of changing an assignment the following information is used:

  • availability of crew members (entity: CrewAvailability)

  • skills of crew members (Crewmember_Skills)

  • flight information: datepart and which balloon (Flight)

  • the needed tasks for a balloon (BalloonTasks)

  • assignment of crew members to tasks for other flights on that datepart (FlightCrew)

Some invariants:

  • there can only be one crew member assigned to a task on a flight

  • a task on a flight can only be assigned to a crew member with at least the needed skills for that task.

  • in order to be assigned a task on a flight, a crew member has to be available on the datepart of the planned flight.

  • in order to be assigned a task on a flight, a crew member must not yet been assigned to another planned flight.

The aggregate to guard the invariants involves at least 5 entities (CrewAvailability, Crewmember_Skills, Flight, BalloonTasks and FlightCrew). As Aggregate Root, by which the actions of the use case are handled, we would normally choose the FlightCrew entity, because it is the entity whose state might be affected by the use case. What I did however, is using an object called “Assigning” as root of the aggregate. The methods needed to implement this use case are only used within this use case and the FlightCrew entity can now be used in another aggregate when needed.


The “Assigning” Aggregate Root (or as I call it: the Assigning Use Case) handles the necessary commands with methods like assign(), unassign(), changeAssignment and guards the invariants. Just like it would otherwise be done by the FlightCrew entity as Aggregate Root. In the case of using the FlightCrew entity as Aggregate Root however, it is more difficult to re-use the FlightCrew entity outside the use case, because you then get a situation of multiple aggregates being involved. The FlightCrew entity is then so to say “promoted” to Aggregate Root and stays an AR in the rest of the application. I limit the “assigning” methods to the use case; they are only used as “role” of those entities within the context of that use case and the state of the FlightCrew-entity cannot be changed outside that use case.

Danil Suits

unread,
Sep 13, 2016, 3:51:43 PM9/13/16
to DDD/CQRS
The “Assigning” Aggregate Root (or as I call it: the Assigning Use Case) handles the necessary commands with methods like assign(), unassign(), changeAssignment and guards the invariants. Just like it would otherwise be done by the FlightCrew entity as Aggregate Root. In the case of using the FlightCrew entity as Aggregate Root however, it is more difficult to re-use the FlightCrew entity outside the use case, because you then get a situation of multiple aggregates being involved. The FlightCrew entity is then so to say “promoted” to Aggregate Root and stays an AR in the rest of the application. I limit the “assigning” methods to the use case; they are only used as “role” of those entities within the context of that use case and the state of the FlightCrew-entity cannot be changed outside that use case.

If I'm reading this correctly, the FlightCrew entity has state, which is generally available, but all writes that evolve a FlightCrew entity from one state to the next have to pass through Assigning?  Other use cases can access FlightCrew state, but without any guarantees that the state isn't being changed by Assigning running somewhere else (ie, the other use cases don't get to lock the state).

And in general, all changes to the state of any given entity in the model will all be enclosed within a single use case.

Close?

Aryeh

unread,
Sep 14, 2016, 3:56:56 AM9/14/16
to DDD/CQRS
The domain is the subject of the problem or system under consideration.  It is represented by the domain model in code.

The domain itself has absolutely nothing to do with terms like bounded context, aggregate and aggregate root. These are development only concepts. No stakeholder, domain expert, or anyone other than those implementing the domain in code, should hear of these concepts.

Modeling a simple car reservation system?  The problem domain only has concepts like Reservation, Customer, Vehicle, Branch.

A Use Case on the other hand can be considered a domain artifact, one that becomes either a representation of a sequence of messages between domain objects (i.e. as described in a sequence diagram), or some external transactional, procedural driver of domain functionality.

The real problem is that few have any idea how to represent domain concepts in a domain model using an object-oriented approach, beyond naming some nouns, ascribing them data, a couple of methods perhaps, then driving them in a procedural way.  This results in what we start to see now in your definition  - that Domain Driven Design has become "Aggregate Driven Design", and that the "Domain Model" has become a "Bounded Context - Aggregate Model".

Best regards,
Aryeh

Herman Peeren

unread,
Sep 14, 2016, 4:59:46 AM9/14/16
to DDD/CQRS
Exactly, "all changes to the state of any given entity in the model will all be enclosed within a single use case": that is true in the balloon crew example. But that unfortunately is just an easy case.

You can make domainmodels where the state of an entity would need to be updated in multiple use cases, like for instance the updating of a bidder's balance in https://groups.google.com/forum/#!topic/dddcqrs/5k5So6i3WsI (decreasing when a higher bid is placed by the bidder, but increasing when someone else places a higher bid or when more credits are bought). Then you can get similar problems as when updating the state of multiple aggregates in one use case. I'm still working on defining a clear set of limitations and solutions for the way I model with "dynamic aggregates", similar to the rule "don't update the state of multiple aggregates in one use case" and the solutions that exist for that (mainly: use eventual consistency).

Also true that "Other use cases can access FlightCrew state, but without any guarantees that the state isn't being changed by Assigning running somewhere else". But the same holds for many other models with concurrent actions. I'm assuming all commands are processed strictly serially, so that would solve those concurrency problems. Am still defining this more precise  however; work in progress.

In whatever way we model a domain, we have to have guarantees that everything stays (eventually) consistent. We have found a way to do that in DDD, but I think we can model in a bit different way, keeping things together that belong together. But exploring this, some problems will surface (like those consistency guarantees) and we have to find solutions for that to make this bit different way of modeling generally applicable. 

Herman Peeren

unread,
Sep 14, 2016, 5:27:34 AM9/14/16
to DDD/CQRS
Yes, one of the reasons I started this discussion is because I see DDD often being degraded into "BC-AR-Modeling", while the true power of DDD is, as its name says, that our design (model) is driven by the domain, not by the technical (implementation) terms or some framework or programming language.

I was once hired to help a team with modeling. The developers talked with the customer (the domain expert) about some database tables. I asked them to stop using technical terms in our sessions and start looking for a common domain language. "But he understands it, for he is already for a long time our customer". My answer: "Our sessions are primarily not to help the customer understand our profession as developers, but to help the developers understand the domain".

Many developers feel comfortable describing the world in technical, not domain-specific terms. Using (and developing) a new language for the domain can make them feel unsecure: they are now the explorers and the learners instead of the ones that provide solutions.

Peter Hageus

unread,
Sep 14, 2016, 5:38:43 AM9/14/16
to ddd...@googlegroups.com
So, essentially changing an established vocabulary because of a people problem? 

There will be technical jargong no matter what, the important thing is keeping it on the implementation side. 'Use case' is a very overloaded term, used in a lot of contexts already, I can’t see how adopting it in this scenario improves anything.

/Peter

Aryeh Hoffman

unread,
Sep 14, 2016, 7:03:15 AM9/14/16
to DDD/CQRS
Hi Herman,

It sounds like your team viewed the domain in terms of a "data model", whereas you were thinking of an object "domain model". Sounds unfortunate :)  Then again, I think that is a common case.

Regards,
Aryeh

@yreynhout

unread,
Sep 14, 2016, 6:29:44 PM9/14/16
to DDD/CQRS
Random musings on what I'm assuming is not some made up problem. Let's hope not, or I've just wasted some time ...

Hm, odd way of running this business ... what information is the planner basing his decisions on to actually plan new flights? Demand, the weather conditions, what leg he used to get out of bed this morning, or a mixture of those? All kidding aside, if you want to maximize profit, you want those things in the air almost 24/7, no? Probably not going to be possible giving weather conditions, limited number of months in which this can happen I would presume, limited amount of staff, pre and post flight tasks, humans need sleep, there's nothing to see anyway at night on a cloudy day, attempt to maximize the number of passengers per flight for the cheaper flights, safety checks that might need to happen, refueling, etc ... you're the expert (or proxy), I'm just guessing.

Planned one year in advance - what if it's bad weather on that datepart, what if a crew member became ill, what if the barn in which the balloons are stored during winter caught fire (hello, insurance)? Probably due to high demand they can actually sell in advance - they must have a good name, service, advertising, ... is there a period in time the planner (is it still him making the call, though) has more insight into conditions that make or break the flight from actually happening? So, crew member are actually committing to this a year in advance or is the planning happening a year in advance but the actual assignment of people way later - kudos if these people manage to set up their agenda a year in advance, though.

Can I loose skills? I mean once obtained is it perpetual? Getting caught for drunk driving will probably mess up the nice flight crew we've assembled. What if I obtain a skill after I'm assigned to a task I'm not yet qualified for but still well before the flight? Would the business welcome that as a way to relax the constraint you have up there? Or do we first need to get the paperwork done and only then the computer will say yes?

What's the most critical resource we're planning? Flight crews or balloon flights? How many planners are there? When should assignment happen? How is the planner notified of a new flight crew proposal? When are crew members supposed to confirm? When are crew members notified of tasks they've been assigned to? What if I have multiple skills, how does the system determine which task I'm more suitable to execute? Can skills conflict?

A lot of the problem space seems to revolve around making sure the flight can actually take place and having everybody commit to it ... Lots of opportunity for concurrency here - what if I obtain/loose a skill while you're planning? what if I change my availability while you're planning (you got that covered with the confirmation step)? what if the balloon became unavailable for future planning while you're planning? and indeed, what if I'm being planned for a task in another flight? Yet assembling a crew for each flight could be a serial activity if you think about it.

Small tip - you are missing a very obvious consistency boundary that eases implementing some of your invariants. I'm not going to influence because I want to first hear how you're going to cover the invariants.


Overall, way better example than user registration ... so kudos for that!

Regards,

Yves.

Greg Young

unread,
Sep 14, 2016, 6:32:49 PM9/14/16
to ddd...@googlegroups.com
Like button is needed in googlegroups.

To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Herman Peeren

unread,
Sep 15, 2016, 4:56:10 AM9/15/16
to DDD/CQRS
Most of your questions come from a combination of not knowing the domain and my simplified showing of this part of the domain.

Hot air balloons can only fly around sunrise and sunset, but most flights are done around sunset (much more popular); morning flights are rarely done in the Netherlands. Also: in the Netherlands most flights are made during the summer season; winter flights are possible but only done very rarely. Hot air balloon companies compete. They all want to get their gondolas (baskets) full. The competition is mostly regional: a company (or a branch of a bigger ballooning company) operates in a specific area. In that region they have fixed spots that are good for departure and that are always used for the flights that were planned in advance. Much of the market is for gifts: a balloon flight is often booked for special occasions, jubilea etc. Often passengers want to make such a flight from a specific location (and hope for instance to sail over their house). So balloon companies put up lists on their websites with when a flight is planned from which departure site for the whole summer season. On the websites you'll find disclaimers that the departure place cannot be guaranteed, but customers book more if you put a concrete place on the list. That is why the information is there a year in advance: it is putting out multiple fishing rods to catch more fish/passengers. BTW, except for the flights that are planned in advance, there also is the possibility to book a private flight (in their advertisement terms called a "VIP flight") and then you'll have a whole basket for your own friends. Done for instance for a mariage proposal (small balloon with just the two lovers and the pilot) or for company events. VIP flights can also be arranged from custom departure places. They are only added to the planning when they are booked. They don't interfere with the regular flights, because the smaller balloons are only used for those private flights and the company events are mostly held on days when less regular flights are planned (there is more demand on or just before a weekend then on working days).

The company I'm talking about has one planner, but when he is on holiday someone else takes over. There is some legacy software for ballooneers, but that is in MS Access and terribly inflexible and not online. However, the company was using it (as are other companies). Planning the crew members is not part of that software. It is mostly the planning of the flights and keep track of how many free places there are on each  flight that was planned in advance. Also the last minutes prices are calculated there (prices and booking is another model and left out of the example); booking a specific flight is cheaper when less days are still to go until the flight. We started making some coupling between the legacy software and the website: putting the list of all planned flights (and how many free places are left) online and the last minutes offers. Booking via the site of course had to decrease the number of available free places in the basket so they won't be overbooked and it had to be synchronised with the legacy software. The online software grew, while the legacy software was still in use. We for instance had to deal with taking the lowest price of last minutes offer, volume discount and discount coupons and use online payment methods (mostly "iDEAL"-payments in the Netherlands). So we started with synchronising with the legacy software, but also started to make our own brand new system that meets the business goals better (for instance: crew planning is not worked out very much in the legacy software), that saves time (no need to synchonise between the legacy software and the online system), is more flexible and online available (when on the spot and a crew member is stuck in traffic or maybe in hospital or whatever, they have to quickly find a new crew member) and prevents errors.

We worked this out for one company (and are still working on it), but are also talking with other companies about using our software and investigating what other software is (being) made by others and in how far we can cooperate with others. For instance next month we have an appointment with a company specialised in selling balloon flights for several other companies; they gather all last minutes, discounts etc. and a flight is booked via them (they get a percentage for it). They also developed some software and we want to have a look if we can better join forces and better exchange data. Challenging: cooperation in a small but highly competitive world of ballooning.

As for the planning much time in advance: yes, the online planning software IS the agenda of the crew-members. Online each crew member has a nice overview of what is planned, confirmed etc. The system sends them e-mails with their upcoming confirmed tasks, from some weeks before the planned flight, and keeps them informed about whether or not the flight is canceled (and even of chances of cancelation; which might be due to the weather conditions but also because there were not enough bookings for the flight - often several planned flights with just a few bookings are put together into one flight: the passengers are so to say moved to another basket; but that is the flight reservation model, not the crew planning model). Crew members can indicate their availability and skills, confirm it, but also unconfirm, indicate (sudden) unavailability and can also indicate that a skill was lost; BTW, the panner can do that too for all crew members. I left that out of the story because it is not relevant here. It never gives concurrency problems in practice: the rare occasions of someone obtaining/loosing a skill never fall on exactly the same moment as them being planned on a flight. Of course the use case of obtaining (or loosing) skills has effects on earlier made plannings; especially when loosing a skill, all coming flights in which that crewmember is planned have to be revisited. Really no problem for something that happens once in a few years...

When a flight is cancelled the passengers have to make a new reservation themselves. Passengers can indicate that they are most interested in a flight around some date OR in a flight around some place. The combination is very hard to realise and we have put quite some effort in making that clear. We use that information when moving passengers from one basket to another (see above: when there were more flights that all had not enough passengers).  You can also put yourself on a list of people interested in a flight taking off from a specific place. The system can send them e-mails of upcoming flights that reasonably match the wanted departed place. Well, all kinds of possibilities, but all in the reservation and booking models, not in the crew-planning. I tell it now to indicate that the whole domain is huge (while the development budget is limited) and my example was just a tiny bit of the whole thing, to show how I implement use cases without "fixed" aggregates.

The choice of the departure places for the flights that are planned for the season is based on the demand for specific departure places in the past (some are more popular), combined with "safe bets": a departure place near the coast can only be used occasionally: when you are sure there will be no off-land wind. But as I said: it is mostly a way to advertise and it is not taken into account (at least: not at the moment) when planning the crew.

The critical resource we are planning in the crew planning model is the flight crew. The list of balloon flights are made up as a list to get passengers to book a flight. It is just a list of all available (bigger) balloons spread over the available dates of the season and a departure place added (because that sells better).

I was trying to keep the example simple, mainly to show the principle of using an entity in several aggregates. I understand the simplification triggers a lot of questions and I hope I've given some more information to answer some of that. But I want to look out not to loose ourselves in this interesting domain and focus on what I am trying to discuss in this thread.

I guess still unaswered from your elaborate posting (thanks a lot) is the most important part at the end: how I implemented those invariants. To be continued soon (but unfortunately no time left right now after typing all this).

Herman Peeren

unread,
Sep 15, 2016, 5:38:08 AM9/15/16
to DDD/CQRS
Availability of crew members is indicated in our system for all dateparts, not just for planned flights. Availability is the result of all addAvailability commands that are issued by the crewmember. They look like: Clear my availability. I'm available on all friday-evenings. I'm available on all thursday-evenings. I'm not available between that date and that date. I'm not available on that specific date. It is all done in the addingAvailability use case. The stream of addedAvailability events results in a different state of the CrewAvailability.

When a VIP-flight is booked or another assigned crew member becomes unavailable (or in the rare case she/he would loose a skill), the crew members that are available (and skilled) for that flight are in the system. They all know they can be called for some last minute emergency when they have indicated they would be available for that datepart.

Most of the messaging in the system is now automatically done per email. We try to have the whole flight crew confirmed at least two weeks in advance. But last minute arranging of another crew-member is of course quicker by phone. At the moment the system provides the telephone numbers of available (and skilled) crew members when needed and the planner calls them. We want to automate more with that too, sending messages to crew members phones.

Herman Peeren

unread,
Sep 15, 2016, 8:58:37 AM9/15/16
to DDD/CQRS
In our system the confirmation of an assignment by the planner is mainly meant as a flag to indicate that from that moment the assignment is visible for the crew member. Before confirmation, the assignment can be changed without the crew member ever knowing that they have been assigned or not. Many personal considerations can play a role in the final (re)assignment and those should not be put into the system. The planner feels more free to change the initial assignments by the system when that is not yet in the open and the crew member is not bothered with premature assignments.

Confirmation of the assignment by the crew member would not be strictly necessary, for it doesn't really change anything in the system. It was added as an extra "heads up" for the crew members to actively check if their availability for that flight is still valid. It puts some extra "weight" on their assignment, makes it a more active commitment.

Only when all tasks for a flight are assigned they can be confirmed by the planner. Later, when an earlier assigned (and possibly confirmed) task becomes open again because of an unexpected unavailability of a crew member, then the other crew members don't have to reconfirm, but they can see there is an open task in their crew. Crew members get an overview of their assignments, but can also see what the other crew members are, have their telephone numbers etc.

About "assembling a crew for each flight could be a serial activity": yes it is, but I'm a bit curious if you meant something else with that. When we have the system propose the first assignments we have it start with the most "demanding" tasks, that is the pilot of the biggest basket, because you need a higher degree of pilot license for it. We only have two skills at the moment: pilot license and driving license and both have degrees: with a higher degree pilot license you can sail a smaller basket, but not the other way around. The same with driving licenses, where you can drive a support car without a trailer when having a license for driving the car with the trailer, but not the other way around. So we first try to provided the biggest needs (assign the most demanding tasks); finding someone with a simple driving license is easiest.

We have no provision in our system yet for forseen skills in the future. The occasion did not occur yet or it did not give any problems yet. But it might be a nice feature to consider. Thanks.


On Thursday, 15 September 2016 10:56:10 UTC+2, Herman Peeren wrote:

Herman Peeren

unread,
Sep 15, 2016, 11:39:48 AM9/15/16
to DDD/CQRS
On Thursday, 15 September 2016 00:29:44 UTC+2, @yreynhout wrote:
Small tip - you are missing a very obvious consistency boundary that eases implementing some of your invariants.

Obvious things to check first are:
  • the flight_id, task_id and crewmember_id in the assign-command must refer to an existing flight, task and crewmember
  • the flight must not be in the past

But maybe you meant something else. Am curious. Will first show some actual code (it was written in PHP, hope you don't mind).

@yreynhout

unread,
Sep 15, 2016, 1:13:53 PM9/15/16
to DDD/CQRS
Interesting domain you have there, Herman. Thank you for taking the time to explain. One can only wish more people would describe their problem space in such detail.

First thing that came to mind, given I have some background in operation(al) research, is the assignment and the nurse scheduling problem. I don't think you need aggregates (or use cases) to solve that. Part of your system emits flight crew proposals for all flights. Any change in input may cause a new "optimal". Ultimately the planner has to (gradually) commit/choose. Any committed flights become a new constraint for the flight crew proposal engine (an intelligent search engine if you will).

The constraints you describe can never really be enforced, now can they? I mean, you can enforce them, but reality can always trump anything that was planned/confirmed. The book of records is ultimately out there - you can just do your best to plan and steer it in the most optimal way, given the reality the system knows about.

Regards,
Yves.

Herman Peeren

unread,
Sep 27, 2016, 3:20:08 PM9/27/16
to DDD/CQRS
Here is the part in the code where the invariants of the assignment of a crewmember to a flight task are checked:


       
// Check the invariants and throw an exception if not OK        
        $this
->taskIsNotYetAssignedToACrewmember($flight, $task);
        $this
->crewmemberIsAvailableForFlight($flight, $crewmember);
        $this
->crewmemberIsQualifiedForTask($crewmember, $task);
        $this
->crewmemberIsNotYetAssignedOnSameDatepart($crewmember, $flight);
       

The whole code of that UseCase where this is from can be found further down.

It is not checked if the crewmember, flight or task actually exist: that is handled by the application layer while making and handling the commands.

It also is not an invariant that an assignment must be for a flight now or in the future: it can be that at the last moment some things are changed (as Yves said: "The book of records is ultimately out there"), but then they still want to have the opportunity to adjust the assignments in the system to what has really happend and that can be done later. This is also to be able to use the actual situation to handle fees of crewmembers and possibly to make decisions for future planning, for instance if available assingments have to be evenly divided among crewmembers (both not yet implemented). The first goal was to get the planning of the crewmembers done.

This application is built (extended from earlier parts) with Doctrine ORM and hardly uses CQRS or Event Sourcing, although ES-ideas are used in the crewmember's indication of availability. A Factory is injected to produce FlightCrewmembers (to make the new entity persistent, while keeping the domain layer and the use case unknowing of the used Doctrine ORM). Also an EventManager(interface) is injected to dispatch events.

@Daniel Plainview: "How does it differ from command handlers then?". I use command handlers in the application layer. They call this UseCase, where invariants are checked and exceptions are thrown if they are violated. The command bus uses middleware to start and end the transaction after which  everything can be persisted. Like in http://simplebus.github.io/DoctrineORMBridge/ and http://tactician.thephpleague.com/plugins/doctrine/ . We try to keep layers for domain, use cases, application and infrastructure separated as much as possible, while at the same time trying to keep things together as much as possible that belong together.


class AssigningCrewmembersToFlightTasks extends UseCase
{
   
/**
     * Factory for FlightCrewmembers
     */

   
private $newFlightCrewmember;

   
/**
     * AssigningCrewmembersToFlightTasks constructor.
     *
     * @param $newFlightCrewmember
     */

   
public function __construct
       
(EventManager $eventManager, NewFlightCrewmember $newFlightCrewmember)
   
{
        parent
::__construct($eventManager);
        $this
->newFlightCrewmember = $newFlightCrewmember;
   
}

   
/**
     * Scenario: assigning a crewmember to a task that is not yet assigned
     * Invoked by AssignCrewmemberToFlightTaskHandler (application layer)
     *
     * @param Crewmember  $crewmember the crewmember to be assigned
     * @param Flight      $flight     the flight it concerns
     * @param BalloonTask $task       the flight task to be assigned
     *
     * @uses parent::$eventManager->dispatch(CrewmemberAssignedToFlightTask)
     *
     * @return  void
     * @throws RuntimeException when invariants are not met
     */

   
public function assign(Crewmember $crewmember, Flight $flight, BalloonTask $task)
   
{
       
// Check the invariants and throw an exception if not OK        
        $this
->taskIsNotYetAssignedToACrewmember($flight, $task);
        $this
->crewmemberIsAvailableForFlight($flight, $crewmember);
        $this
->crewmemberIsQualifiedForTask($crewmember, $task);
        $this
->crewmemberIsNotYetAssignedOnSameDatepart($crewmember, $flight);
           
       
// Assign crewmember to flightTask
        $flight
->addCrewmember(
            $this
->newFlightCrewmember->create($flight, $task, $crewmember)
       
);

       
// Dispatch a CrewmemberAssignedToFlightTask event
        $this
->eventManager->dispatch(
           
new CrewmemberAssignedToFlightTask(
                $flight
->getId(), $task->getId(), $crewmember->getId()
           
)
       
);
   
}

   
/**
     * Scenario: unassigning a crewmember from a task.
     * Invoked by UnassignCrewmemberFromFlightTaskHandler.
     *
     * @param Flight      $flight the flight it concerns
     * @param BalloonTask $task   the flight task to be assigned
     *
     * @uses parent::$eventManager->dispatch(CrewmemberUnassignedFromFlightTask)
     *
     * @return void
     * @throws RuntimeException when invariants are not met
     */

   
public function unAssign(Flight $flight, BalloonTask $task)
   
{
       
// Check the invariants and throw an exception if not OK
       
// Get the flightcrew-member to whom this task was assigned to      
        $flightCrewmember
= $this->taskIsAssignedToACrewmember($flight, $task);
       
       
// remove flight task as being assigned
        $flight
->removeCrewmember($flightCrewmember);

       
// Dispatch a CrewmemberAssignedToFlightTask event
        $this
->eventManager->dispatch(
           
new CrewmemberUnassignedFromFlightTask(
                $flight
->getId(), $task->getId(),
                $flightCrewmember
->getCrewmember()->getId()
           
)
       
);
   
}

   
/**
     * Scenario: assigning a crewmember to an already assigned task.
     * First unassign it, then assign,
     * taskIsNotYetAssignedToACrewmember is superfluously checked at assignment.
     * Invoked by ReassignCrewmemberToFlightTaskHandler
     *
     * @param Crewmember  $crewmember the crewmember to be assigned
     * @param Flight      $flight     the flight it concerns
     * @param BalloonTask $task       the flight task to be assigned
     *
     * @uses $this->unAssign($flight, $task)
     * @uses $this->assign($crewmember, $flight, $task)
     *
     * @return  void
     * @throws RuntimeException when invariants are not met
     */

   
public function reAssign
       
(Crewmember $crewmember, Flight $flight, BalloonTask $task)
   
{
        $this
->unAssign($flight, $task);
        $this
->assign($crewmember, $flight, $task);
   
}

   
// ========== methods to check invariants ==========

   
/**
     * This flight task should not yet be assigned to a crewmember
     *
     * @param Flight      $flight the flight it concerns
     * @param BalloonTask $task   the flight task to be assigned
     *
     * @throw RuntimeException
     * @return void
     */

   
private function taskIsNotYetAssignedToACrewmember
       
(Flight $flight, BalloonTask $task)
   
{
       
// Check the assigned tasks of this flight
        $assigned
= $flight->findFlightCrewmember($task);
       
if (! is_null($assigned)) {
               
throw new RuntimeException(
                   
'This flight task has already been assigned to a crewmember'
               
);
       
}
   
}

   
/**
     * This crewmember should be available for this flight
     *
     * @param Flight     $flight     the flight it concerns
     * @param Crewmember $crewmember the crewmember to be assigned
     *
     * @throw RuntimeException
     * @return void
     */

   
private function crewmemberIsAvailableForFlight
       
(Flight $flight, Crewmember $crewmember)
   
{
       
if (!$crewmember->isAvailableOn($flight->getDatepart())) {
           
throw new RuntimeException(
               
'This crewmember is not available on the datepart of this flight'
           
);
       
}            
   
}

   
/**
     * This crewmember should be qualified for this flight task
     *
     * @param Crewmember  $crewmember the crewmember to be assigned
     * @param BalloonTask $task       the flight task to be assigned
     *
     * @throw RuntimeException
     * @return void
     */

   
private function crewmemberIsQualifiedForTask
       
(Crewmember $crewmember, BalloonTask $task)
   
{
       
if (!$crewmember->isQualifiedForTask($task)) {
           
throw new RuntimeException(
               
'This crewmember is not qualified for this task'
           
);
       
}
   
}

   
/**
     * This crewmember should not yet be assigned
     * to another task on the same datepart
     *
     * @param Crewmember $crewmember the crewmember to be assigned
     * @param Flight     $flight     the flight it concerns
     *
     * @throw RuntimeException
     * @return void
     */

   
private function crewmemberIsNotYetAssignedOnSameDatepart
       
(Crewmember $crewmember, Flight $flight)
   
{
       
if (!$crewmember->isNotYetAssignedOn($flight->getDatepart())) {
           
throw new RuntimeException(
               
'This crewmember is already assigned on the datepart of this flight'
           
);
       
}
   
}

   
/**
     * This flight task should be assigned to a crewmember
     *
     * @param Flight      $flight the flight it concerns
     * @param BalloonTask $task   the flight task to be unassigned
     *
     * @throw RuntimeException
     * @return FlightCrewmember $flightCrewmember to which task was assigned
     */

   
private function taskIsAssignedToACrewmember
   
(Flight $flight, BalloonTask $task)
   
{
       
// Check the assigned tasks of this flight
        $assigned
= $flight->findFlightCrewmember($task);
       
if (is_null($assigned)) {
           
throw new RuntimeException(
               
'This flight task has not yet been assigned to a crewmember'
           
);
       
}

       
return $assigned;
   
}
}



On Thursday, 15 September 2016 10:56:10 UTC+2, Herman Peeren wrote:

Herman Peeren

unread,
Sep 27, 2016, 3:25:02 PM9/27/16
to DDD/CQRS
Last part of code was truncated. Not very exciting, but for completeness:

           
throw new <span style="col

Herman Peeren

unread,
Sep 28, 2016, 4:38:17 AM9/28/16
to DDD/CQRS

Key thing I wanted to show with the example is: here the invariants are guarded in the UseCase, not in for instance the $flight which could have been made an aggregate root. I've done that, because those invariants are only relevant in the context of that specific use case and not in other uses of the domain objects.


In PHP we don't have "friend functions" like in C++, with which we could limit the use of the addCrewmember()-method in the Flight class to the Assigning UseCase. It would be stricter if I would not have an addCrewmember()-method in the Flight class at all. Room for improvement.


With this separation between entities and use cases I have the Clean Architecture picture from https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html in mind:





On Tuesday, 27 September 2016 21:20:08 UTC+2, Herman Peeren wrote:

           
throw new <span style="col

Daniel Plainview

unread,
Sep 28, 2016, 10:46:04 AM9/28/16
to DDD/CQRS
Hi, 

I probably was inattentive, but I don't understand why the Flight aggregate cannot take care of itself, e.g.

$flight->assignCrewmember($crewmember, $task, ...); // It checks all those invariants and adds crewmember

? I mean, your model (Flight) allows to bypass invariants by calling $flight->addCrewmember() outside of the use case. Does it make sense? Doesn't your Flight model become anaemic?

Further, the Flight can record events by itself as well, because it knows about internal changes better than others. 

           
throw new <span style="col

Daniel Plainview

unread,
Sep 28, 2016, 10:48:49 AM9/28/16
to DDD/CQRS
In PHP we don't have "friend functions" like in C++, with which we could limit the use of the addCrewmember()-method in the Flight class to the Assigning UseCase. It would be stricter if I would not have an addCrewmember()-method in the Flight class at all. Room for improvement.

OK, I see the point.

However, still, I'm not sure why you can't put all this logic in to the aggregate itself.

Herman Peeren

unread,
Sep 28, 2016, 3:22:20 PM9/28/16
to DDD/CQRS
On Wednesday, 28 September 2016 16:48:49 UTC+2, Daniel Plainview wrote:
However, still, I'm not sure why you can't put all this logic in to the aggregate itself.

Because the functionality of assigning crewmembers to tasks on that flight is only used in that use case, nowhere else. It is in fact the responsability of the use case, not of the flight. If you attach that functionality to the flight (or to one of the other objects as root of that aggregate) you'll have that functionality available everywhere, also outside the use case. Whereas the only context where that functionality is applicable (and therefore should be available) is within the use case.

I would even prefer to not have a addCrewmember()-method in the Flight class at all. Only methods to query a flight for crewmembers or not yet assigned tasks etc. (or maybe even that not if it is only used within the scope of a use case). And have all methods to assign crewmembers to flight tasks only within the scope of the assigning use case. It keeps the Flight class clean and all relevant behaviour for the use case is at one point.

The Flight class is also used in other (state changing) functionality, like reservations of passengers, keeping track of the number of available free places, or
moving the same flight to another date,or joining passengers of not fully booked baskets into one flight, or calculating total passenger weight and the needed amount of propane, or calculating last-minutes prices,  putting them on the website to use them to calculate the right price, etc. If all those would become the responsability of the Flight class you'll clutter a lot of functionality on that class. Like you often see with bloated User classes.

The functionality/behaviour has a scope and the invariants to be guarded have that same scope. I limit the invariants to the behaviour, not to the structure, the entities. It is all part of making behaviour a first class citizen, not something hidden in a class. See the now more than 10 year old http://steve-yegge.blogspot.nl/2006/03/execution-in-kingdom-of-nouns.html for a nice story about estimating nouns higher than verbs.

In DCI they go one step further: they add the richer behaviour to the objects only when needed (within the use case). That is not the same as a complete anemic model (data) and a procedural implementation of behaviour. In DCI the methods are litterary added to the objects only within the scope of the use case. So you'll then have an
addCrewmember()-method in the Flight class only within that Assigning use case. That however is not easily realised in our current class oriented languages. Because I have to use PHP in this project it will never be 100 % ideal. But I am still looking for means to solve the problem of not putting behaviour (methods) in classes that are only used in specific use cases and at the same time trying to keep behaviour together that belongs together (within the scope of the use case).

Our models and implementations change when looking from the perspective of behaviour instead of things/data/objects. Our OOP languages are traditionally more from a structural perspective (being) instead of behavioural (behaving) and temporal (becoming). Event Sourcing is one of those paradigms that changes that perspective. Thinking in aggregates (and especially thinking in mutually exclusive aggregates) is mainly coming from the structural way we looked at things, with class diagrams evolving from ERDs. A new way of looking at things needs exploring, a lot of try and error and sometimes questioning if the way we did it is still the right way or can be improved.

A solution for those bloated classes that is often used by DDD-practitioners is to split in different models (in "Bounded Contexts", preferrable written as BC). The splitting is OK, but I would call that splitting into modules, not in different BCs, because the boundaries of a model should be where the meaning of the concept are consistent. On the theoretical side however you could argue that a different behaviour of an object gives a different meaning, but putting that line of reasoning further you would end up calling every use case a different Bounded Context. How to split things up is one of the most important aspects of modeling and we should try to sail between the Scylla and Charybdis of monolyth and splintered. I'm investigating seams along the scope of behaviour.

Daniel Plainview

unread,
Sep 28, 2016, 5:52:55 PM9/28/16
to DDD/CQRS
> Because the functionality of assigning crewmembers to tasks on that flight is only used in that use case, nowhere else. It is in fact the responsability of the use case, not of the flight. If you attach that functionality to the flight (or to one of the other objects as root of that aggregate) you'll have that functionality available everywhere, also outside the use case. Whereas the only context where that functionality is applicable (and therefore should be available) is within the use case. 
> ...
> If all those would become the responsability of the Flight class you'll clutter a lot of functionality on that class. Like you often see with bloated User classes.

So it's all about the Bounded Context concept denial.

> I limit the invariants to the behaviour, not to the structure, the entities

Wouldn't be you happy with something like partial classes like in C#, so you can make every method of an aggregate in different files? Every file (method) is a "use case", you can't change the state outside of these files ("use cases").

> The splitting is OK, but I would call that splitting into modules, not in different BCs, because the boundaries of a model should be where the meaning of the concept are consistent

I have feeling that you don't like BC concept because sometimes it leads to some sort of duplication of your model. Sometimes you may have similar User aggregates in different BCs, so you think about it as about same model. Duplication is the price for isolation and independence.

> but putting that line of reasoning further you would end up calling every use case a different Bounded Context

Bounded Context is yet another tool to follow the SRP. SRP is not about "every class must have the only method". Responsibility of a model is to represent a concept from a domain.
Different use cases doesn't always break this responsibility, it may still represent a concept.

Johanna Belanger

unread,
Sep 28, 2016, 6:22:49 PM9/28/16
to DDD/CQRS
I've been following this discussion with interest because it's given me hope of finally understanding the point of DCI which I have tried and failed at a few times, and also because our use of the term Bounded Context on this list often seems fuzzy to me. Thank you for sharing your domain to enable these detailed discussions.

May I ask what criteria you use to determine the boundaries between use cases? For example, why did you choose to include both assign and unassign methods in your Assigning use case?

Danil Suits

unread,
Sep 28, 2016, 11:44:45 PM9/28/16
to DDD/CQRS

On Wednesday, September 28, 2016 at 9:48:49 AM UTC-5, Daniel Plainview wrote:
However, still, I'm not sure why you can't put all this logic in to the aggregate itself.

I'm beginning to suspect that he has.

On Wednesday, September 14, 2016 at 3:59:46 AM UTC-5, Herman Peeren wrote:
I'm assuming all commands are processed strictly serially, so that would solve those concurrency problems.
 

My understanding of Herman's model, is something like this

1) The entire model is contained within a single consistency boundary; any command effectively locks down the state of everything entity in the design until the processing is completed.
1a) From which it follows that the model (all of it) is the aggregate.

2) The design has no explicit aggregate root; at least, not one that I have been able to identify in the design thus far.

3) The definition of the business invariant, which in an OO design would normally be implemented within the entity command methods, are instead given first class status as use cases.

4) The entities themselves are data structures.  Which is to say, that they are state.  It's not all that hard to envision that the arguments being passed into the use cases could be value types
 

   
public function assign(Crewmember $crewmember, Flight $flight, BalloonTask $task)
   
{
       
// Check the invariants and throw an exception if not OK        
        $this
->taskIsNotYetAssignedToACrewmember($flight, $task);
        $this
->crewmemberIsAvailableForFlight($flight, $crewmember);
        $this
->crewmemberIsQualifiedForTask($crewmember, $task);
        $this
->crewmemberIsNotYetAssignedOnSameDatepart($crewmember, $flight);
       

        // update the flight
        $flight = $flight->addCrewmember($
crewmember);
     
 
       
// Dispatch a CrewmemberAssignedToFlightTask event

        $this
->eventManager->dispatch(
           
new CrewmemberAssignedToFlightTask(
                $flight
->getId(), $task->getId(), $crewmember->getId()
           
)
       
);

  
       return $flight


   
}


Although in use cases where you are modifying multiple entities, that could get a little bit clumsy to write.



On Wednesday, September 28, 2016 at 2:22:20 PM UTC-5, Herman Peeren wrote:
Our OOP languages are traditionally more from a structural perspective (being) instead of behavioural (behaving) and temporal (becoming)

I assume that State/Queries is analogous to being, and Entity/Commands analogous to becoming -- something I picked up from Stuart Halloway's description of the epochal time model (rebranded as the unified succession model?).  Not entirely sure where "behaving" belongs.  I could use some clarification on what you are going after here.

Herman Peeren

unread,
Sep 29, 2016, 3:17:19 AM9/29/16
to DDD/CQRS
Thank you all for your interesting remarks! Really helpful. I will reply them all in detail. But first I will refactor the code and model of the example a bit, based on some ideas I had last night, caused by this discussion. I will remove the addCrewmember() method from the Flight class. I will share some insights this excercise yielded, like the role of Repositories and the connection with REST. Work in progress...

Herman Peeren

unread,
Sep 29, 2016, 1:38:37 PM9/29/16
to DDD/CQRS

Refactoring the AssigningCrewmembersToFlightTasks UseCase

Namechange

I changed the name of the FlightCrewmember class to CrewmemberAssignment. This object holds the information which crewmember is assigned to which task of a flight. This is the entity that retrieves its data via the ORM from the join-table of the many-to-many relationship between a flight and crewmembers (fullfilling the tasks that the used balloon requires). In the table we currently have records with a flight_id, task_id and crewmember_id. The word “assignment” better describes the information this object represents. A simple name change but it triggered other thoughts.

Responsibility

I wanted to take the responsability for the assignment away from the Flight class. The flight can query what tasks are needed, what tasks are assigned (and consequently what is still open). But the flight cannot assign crewmembers to its tasks anymore. Of course, under the hood you could still just do an add() or removeElement() on the Doctrine Collection that forms the assigned crew at that moment, but there is no public method to do that directly on the flight-object anymore.

Adding, removing and persistence: Repository and Factory

As said, I had given the responsibility for assigning to the Assigning use case. But where are the Assignment-objects added to and where is that done? It is not the Assignment-objects themselves, but something that manages the collection of those objects. So I came to the Assignment Repository. A Repository has two legs: one is standing in the domain layer (interface: managing a collection of objects) and the other in the infrastructure layer (managing persistence). It is a mediator between those two layers. In Scott Millet's book, chapter 21, NHibernate is used as example and an interface is used for both retrieving from the database (the find-methods) as for adding and removing objects to and from the collection, persisting those changes from there in the database. But as Eric Evans describes in his chapter about Repositories (page 147-167), the Repository pattern was originally not intended for storing newly created objects, merely for reconstituting objects from the database. A Factory on the other hand handles the beginning of an object's life. In this balloon-project we use Doctrine ORM and a Repository is there only used for retrieving objects from the database; no add() or remove() in that interface.

So I use a CrewmemberAssignmentFactory to create a new CrewmemberAssignment-object (and make it manageable to be persisted when the whole transaction is successfuly ended). No need to explicitly add it to a crew-collection of the flight: when the transaction is committed (= when the Unit of Work is flushed) the database and the collections in various entities are synchronised. My domain layer doesn't have to know about that: it is completely agnostic of any persistence. Clean separation of concerns in the different layers.

Now I still needed to be able to remove a CrewmemberAssignment object. As the Doctrine Repository was no option I had a look at my CrewmemberAssignmentFactory again. That may seem odd at first and maybe it would need another name than Factory, but when looking at it from a bit abstract level removing is a kind of negative adding. One is moving an object into a collection (and persist that in the database) the other is moving it out of the collection (and persist that in the database). You can always transform those two different actions into one (plus an extra boolean parameter to give the direction) and then persist that append-only. I had also used that with the objects that indicate the availability of crewmembers: they indicate if a crewmember is available or unavailable on the given days, dateparts or date-ranges. I will come back on this in my answer to Johanna's question why I choose to include both assign and unassign methods in my Assigning use case. Also see Greg's “Answering a question”-talk from june 2014 https://skillsmatter.com/skillscasts/5437-answering-a-question#video where he uses an example with a kind of “negative membership” instead of removing a member. In short: besides an add()-method I made a remove()-method in the CrewmemberAssignmentFactory to let the infrastructure know the removal of the CrewmemberAssignment has to be persisted in the database too.

Now both creating and deleting persistable objects were with a simple method in that Factory (or whatever name we should give it), I looked at the possibility to also have the invariants guarded there. But that is not a good idea: that business logic belongs to the use case and not to the infrastructural layer. The only responsability of that “Factory” is to create and remove objects in such a way that it can be persisted.

Nouns, verbs, join-tables and REST

In a relational database we have a join-table with foreign keys from tables that have a many-to-many relationship. Between the flights-table and the crewmember-table I had such a FlightCrewmember-table (also having a column for the task that the crewmember is assigned to). From there the entity holding the collection of crewmembers assigned to a flight was first also called FlightCrewmember. The same with a CrewmemberSkill table for the many-to-many relation between crewmembers and skills. After thinking a bit what is actually meant by this FlightCrewmember-object, I renamed it to CrewmemberAssignment (first named it CrewmemberToFlightTaskAssignment, but that is unnecessary longer). I did the same with the CrewmemberSkill-object and renamed that CrewmemberQualification.

So I have a verb imperatively describing an action: assign or qualify. I use that same verb also in the form of a past participle for events (assigned or qualified). In the form of a present participle for use cases (assigning or qualifying). And now I also use a noun (assignment, qualification) as name for an entity, a thing that is added and removed. Interesting how you can switch from verb to noun and vice-versa in the same context but with a bit different use. It reminds me of switching from RPC to REST, where you add to an assignment-resource instead of remotely calling an assign-procedure and delete from that assignment-resource instead of remotely calling an unassign-procedure. Rich behaviour of objects can always be translated to a simple, uniform CRUD-interface with a rich variety of objects (resources) and vice-versa. One is not necessarily better than the other; it is all about the usefulness in different situations.

Code

The code of the invariants was unchanged. Except for the above mentioned refactoring I did some minor changes. Currently the code of the 3 public methods of the AssigningCrewmembersToFlightTasks use case, without the docblocks, looks like this:


   
public function assign(Crewmember $crewmember, Flight $flight, BalloonTask $task)
   
{
       
// Check the invariants and throw an exception if not OK        
        $this
->taskIsNotYetAssignedToACrewmember($flight, $task);


        $this
->crewmemberIsAvailableForFlight($crewmember, $flight);


        $this
->crewmemberIsQualifiedForTask($crewmember, $task);
        $this
->crewmemberIsNotYetAssignedOnSameDatepart($crewmember, $flight);


           
       
// Add assignment of crewmember to flightTask
        $crewmemberAssignment
           
= $this->crewmemberAssignmentFactory->add($flight, $task, $crewmember);



       
// Dispatch a CrewmemberAssignedToFlightTask event
        $this
->eventManager->dispatch(


           
new CrewmemberAssignedToFlightTask($crewmemberAssignment)
       
);
   
}

   
public function unAssign(Flight $flight, BalloonTask $task)


   
{
       
// Check the invariants and throw an exception if not OK


       
// Get the CrewmemberAssignment to whom this task was assigned to      
        $crewmemberAssignment
= $this->taskIsAssignedToACrewmember($flight, $task);


       
       
// remove flight task as being assigned


        $this
->crewmemberAssignmentFactory->remove($crewmemberAssignment);

       
// Dispatch a CrewmemberUnassignedFromFlightTask event
        $this
->eventManager->dispatch(
           
new CrewmemberUnassignedFromFlightTask($crewmemberAssignment)
       
);
   
}

   
public function reAssign
       
(Crewmember $crewmember, Flight $flight, BalloonTask $task)


   
{
       
// Check the invariants and throw an exception if not OK


        $this
->crewmemberIsAvailableForFlight($crewmember, $flight);


        $this
->crewmemberIsQualifiedForTask($crewmember, $task);
        $this
->crewmemberIsNotYetAssignedOnSameDatepart($crewmember, $flight);


       
// Get the CrewmemberAssignment to whom this task was assigned to      
        $crewmemberAssignment
= $this->taskIsAssignedToACrewmember($flight, $task);

       
// --- Remove flight task as being assigned ---
        $this
->crewmemberAssignmentFactory->remove($crewmemberAssignment);

       
// Dispatch a CrewmemberUnassignedFromFlightTask event
        $this
->eventManager->dispatch(
           
new CrewmemberUnassignedFromFlightTask($crewmemberAssignment)
       
);

       
// --- Add assignment of crewmember to flightTask ---
        $this
->crewmemberAssignmentFactory->add($flight, $task, $crewmember);




       
// Dispatch a CrewmemberAssignedToFlightTask event
        $this
->eventManager->dispatch(


           
new CrewmemberAssignedToFlightTask($crewmemberAssignment)
       
);
       
   
}

Herman Peeren

unread,
Sep 29, 2016, 1:57:59 PM9/29/16
to DDD/CQRS
On Thursday, 29 September 2016 00:22:49 UTC+2, Johanna Belanger wrote:
May I ask what criteria you use to determine the boundaries between use cases? For example, why did you choose to include both assign and unassign methods in your Assigning use case?

In my posting about refactoring above I wrote:
when looking at it from a bit abstract level removing is a kind of negative adding. One is moving an object into a collection (and persist that in the database) the other is moving it out of the collection (and persist that in the database). You can always transform those two different actions into one (plus an extra boolean parameter to give the direction) and then persist that append-only. I had also used that with the objects that indicate the availability of crewmembers: they indicate if a crewmember is available or unavailable on the given days, dateparts or date-ranges. I will come back on this in my answer to Johanna's question why I choose to include both assign and unassign methods in my Assigning use case. Also see Greg's “Answering a question”-talk from june 2014 https://skillsmatter.com/skillscasts/5437-answering-a-question#video where he uses an example with a kind of “negative membership” instead of removing a member. In short: besides an add()-method I made a remove()-method in the CrewmemberAssignmentFactory to let the infrastructure know the removal of the CrewmemberAssignment has to be persisted in the database too.
Unassigning is a kind of negative assigning. It is both changing the assignments. When you need one, you often need the other, in some form. And in re-assigning you need functionality and invariants of both.
It is in the context of my quest to keep things together that belong together, not scattering algorithms and cohesive behaviour over a lot of very small methods and classes in which it is difficult to understand what is going on by reading the code.

Herman Peeren

unread,
Sep 29, 2016, 3:59:01 PM9/29/16
to DDD/CQRS
On Wednesday, 28 September 2016 23:52:55 UTC+2, Daniel Plainview wrote:
(...)
So it's all about the Bounded Context concept denial.

Haha, 'denial' sounds as a pretty severe offence ;-)

I dont 'deny' any concept of a Bounded Context. I know what is meant by it and I'm only looking at ways to express that message more clearly, also in the hope it is better understood outside the small group of DDD-insiders. I totally agree that it is extremely important to define the boundaries of models very explicitly. The language used to build a model is only applicable within that model. The boundaries of the model are boundaries for the sufficiently unambiguous meaning of words in that language in order to be able to communicate between the different stakeholders/teammembers to come to a useful model with which value-adding software can be implemented.

At the moment the concept of Bounded Context is not precisely defined and it is used ambiguously. Like defining it as a boundary and a few sentences later talk about the boundaries of a Bounded Context. I probably understand what is meant, but the communication about a model and making its boundaries explicit might be improved. My proposal is to stop using the vague word Bounded Context (although I will even miss the almost poetric sound after all those years) and talk about the boundaries of our models.

 
On Wednesday, 28 September 2016 23:52:55 UTC+2, Daniel Plainview wrote:
Wouldn't be you happy with something like partial classes like in C#, so you can make every method of an aggregate in different files? Every file (method) is a "use case", you can't change the state outside of these files ("use cases").

In PHP we have traits, just like in Scala. With that you can easily add methods to classes. But just like with partial classes in C# it is adding specific behaviour to classes at compile time, contrary to adding behaviour to objects at runtime (hence Cope's rant against "class oriented programming"). Don't misunderstand me, I'm not an uncritical apostle of DCI, I'm always putting questionmarks, discussing and above all thinking myself. I'm just trying to learn from other paradigms and trying to understand new points of view.


On Wednesday, 28 September 2016 23:52:55 UTC+2, Daniel Plainview wrote:
I have feeling that you don't like BC concept because sometimes it leads to some sort of duplication of your model. Sometimes you may have similar User aggregates in different BCs, so you think about it as about same model.

See above: I think it is very important to split models in different models to keep the language within that model consistent. Otherwise the same word is used with (slightly) different meaning and that is not good. So I do want to split a model in smaller models when that is useful, but I don't want to split it in smaller models than necessary. And besides splitting in different models we can (and should) also split in different modules, aggregates, use cases or whatever other splittings are useful. However: splitting too far (splintering) is as useless as not splitting enough (monolyth). And sometimes it can also be useful to unify (parts of) different models.


On Wednesday, 28 September 2016 23:52:55 UTC+2, Daniel Plainview wrote:
Duplication is the price for isolation and independence.
That is a nice sentence, thanks. I always say: "redundancy is your friend", which expresses a similar thought. You see: we don't disagree on this point.
 
 On Wednesday, 28 September 2016 23:52:55 UTC+2, Daniel Plainview wrote:
Bounded Context is yet another tool to follow the SRP. SRP is not about "every class must have the only method". Responsibility of a model is to represent a concept from a domain.

I think the main point of the boundaries of a model is (enough) consistency of meaning of the vocabulary. The integrity of the model is a requirement for communication among the different stakeholders. We also don't disagree about what SRP is and it can often be a useful concept to see if we should split somthing or not, but it doesn't tell us if you can better split a specific part in different models, different aggregates, different use cases, or different modules. What I meant to say is that the main criterium of splitting a model is the ambiguity in the vocabulary.

  On Wednesday, 28 September 2016 23:52:55 UTC+2, Daniel Plainview wrote:
Different use cases doesn't always break this responsibility, it may still represent a concept.
 
See above. It looks like you want to split up and use a hierarchy of categories, with different names, so Bounded Context is a higher level and Use Case is a lower level, but the criteria to split in one or the other is both SRP. But if the principle to split things are exactly the same, then we should give those categories a similar name; like level1 BC, level-2 BC etc... I think it is not just another name for a different level of category in which to split things up. I'm trying to get that more clear.
 

Danil Suits

unread,
Sep 29, 2016, 3:59:20 PM9/29/16
to DDD/CQRS
On Thursday, September 29, 2016 at 12:38:37 PM UTC-5, Herman Peeren wrote:

Refactoring the AssigningCrewmembersToFlightTasks UseCase


Two particular alarms went off in my head when reading this.


> No need to explicitly add it to a crew-collection of the flight: when the transaction is committed (= when the Unit of Work is flushed) the database and the collections in various entities are synchronised.

I'm troubled by the fact that the persistence of new entities created by the factory is implicit, rather than explicit.  It seems to be "clever", that is to say, a surprise.  This stuff is hard enough to get right even when we are boring.


> I changed the name of the FlightCrewmember class to CrewmemberAssignment.  This object holds the information which crewmember is assigned to which task of a flight.

The notion that there's the entity in the role of Crewmember who hasn't actually been assigned to the flight crew would drive me absolutely bonkers.  My instinct is that there really should be ubiquitous language for flight qualified employees that is independent of their current assignments in the duty roster.

Also CrewmemberAssignment sounds backwards.  We aren't starting with a flight, and Bob, and trying to find something for Bob to do; we're starting with a flight and a responsibility (pilot!) and trying to ensure that the responsibilities are all being covered by somebody qualified to handle them.  So I would expect TaskAssignment/DutyAssignment, rather than CrewAssignment.  Perhaps I'm drawing from other domains, where you might have crew pulling multiple duties on a single flight.

The name for this thing, whatever it is, should reflect what's used in the business; if it is a crewmember assignment, then so be it... unless the business is open to the suggestion that a clearer language should be adopted.  We are allowed to improve the business as part of this modeling exercise, after all.



Herman Peeren

unread,
Sep 29, 2016, 4:30:49 PM9/29/16
to DDD/CQRS
Sorry, have to stop now, but short reaction:
  • we started with the question from the business of crew planning.They want to have crewmembers indicate when they are available and what their skills are then want to have an optimal planning. One of the problems with ballooning (at least here in Europe) is, the wheather can hardly be accurately predicted. Besides that there can always come a extra flight at the last moment. And some crewmember can become unavailable at the very last moment. They want to have everything prepared and also know if there is some backup.
  • they use "crew" both for the people who are potentially available as crew as for the actual crew when they fly.
  • your alarm about the persistence not being part of the domain (sorry, only read quickly) is going in the direction of questioning the validity of Doctrine.

All those points are interesting and thank you for bringing it up  but not the subject of this thread. The domain is just a concrete example so we are not only talking theoretically. It is much much more elaborate than what I show here; it is a concrete, but simplified example. Although I appreciate all ideas about the domain and the modeling of it I want  to take care that I won't loose the focus of this thread: that I see validity in giving use cases (behaviour) a more central role in my models than aggregates (structure).


I'll more closely read it tomorrow and hope to formulate some answer (besides the other not yet answered reactions).

Raoul Duke

unread,
Sep 29, 2016, 4:31:33 PM9/29/16
to ddd...@googlegroups.com

keep up the good work!
i want to he rich so i can hire / throw money at you.

Herman Peeren

unread,
Sep 30, 2016, 4:09:24 AM9/30/16
to DDD/CQRS
From some reactions you might think I´m advocating to put everything into one model, but that is not the case.

In the ballooning-example for instance we have different models for:
  • reservations and bookings by passengers, keeping track of free places left in a basket, last minute prices etc.
  • pictures and reviews of a flight, including downloading originals in a zip, sharing on social media etc.
  • crew-planning

The example in this thread is only about the crew-planning.


All those models share concepts, like a Flight or Balloon, but they all use different aspects of that concept. We might split into more models and refactor to a Shared Kernel. And there is a lot more that will get its own model when it will be developed further (group flights, gift vouchers, the coupling to accounting, friends vouchers for passengers via their network, etc.); plans enough.


On the other hand: this example is from an application we have been and will be working on over a longer period of time with only a very small team (mainly due to the very limited budgets). In compliance with Conway's Law the small team will be reflected in a tendency to avoid splitting models up very much. But that is more from practical than  theoretical motives.


On Wednesday, 28 September 2016 23:52:55 UTC+2, Daniel Plainview wrote:
Message has been deleted

Herman Peeren

unread,
Oct 1, 2016, 5:27:48 AM10/1/16
to DDD/CQRS
On Thursday, 29 September 2016 21:59:20 UTC+2, Danil Suits wrote:
> I changed the name of the FlightCrewmember class to CrewmemberAssignment.  This object holds the information which crewmember is assigned to which task of a flight.

The notion that there's the entity in the role of Crewmember who hasn't actually been assigned to the flight crew would drive me absolutely bonkers.  My instinct is that there really should be ubiquitous language for flight qualified employees that is independent of their current assignments in the duty roster.

Also CrewmemberAssignment sounds backwards.  We aren't starting with a flight, and Bob, and trying to find something for Bob to do; we're starting with a flight and a responsibility (pilot!) and trying to ensure that the responsibilities are all being covered by somebody qualified to handle them.  So I would expect TaskAssignment/DutyAssignment, rather than CrewAssignment.  Perhaps I'm drawing from other domains, where you might have crew pulling multiple duties on a single flight.


Assignment is to both sides: we assign a crewmember to a task of the flight and we assign at the same time a task to a crewmember. It is just from which side you look at it. I changed the name of the Assignment to the longer CrewmemberToFlightTaskAssignment to express more precisely what is meant.

The task is defined in such a way that there is exactly one person for one task (and vice-versa) and even: only one (minimum) skill for one task. There are only 2 skills involved: driving license and pilot license. A pilot only needs a pilot license, for when (s)he is flying (s)he cannot drive a support car to the landing. A crewmember in a support car only needs a driving license.


 On Thursday, 29 September 2016 21:59:20 UTC+2, Danil Suits wrote:
The name for this thing, whatever it is, should reflect what's used in the business; if it is a crewmember assignment, then so be it... unless the business is open to the suggestion that a clearer language should be adopted.  We are allowed to improve the business as part of this modeling exercise, after all.

We always talk about crewmembers as if it were a profession. Like a nurse is still a nurse even when not at work or a worker is a worker when not at work. But thanks for the heads up about this double use of the word, for we are so used to it that I would almost forget that it might seem a bit odd for someone from the outside to talk about crewmembers when they are not on duty.

We did change some words from the domain. Initially they talked about crewmembers and pilots as two different things, also in the legacy software (in Access). But for the planning it was easier to talk about crewmembers and a pilot is just one of the tasks. The ballooning company had shirts with "crew" on them and both the pilot and the oher crewmembers wore them. So, in a way they already adopted the idea of pilots being part of the crew. We talked about it and made it explicit.
 





 

Herman Peeren

unread,
Oct 1, 2016, 9:01:39 AM10/1/16
to DDD/CQRS
On Thursday, 29 September 2016 05:44:45 UTC+2, Danil Suits wrote:
My understanding of Herman's model, is something like this

1) The entire model is contained within a single consistency boundary;

That is to say: consistency of the language. All words/concepts used in a model should (sufficiently) have the same meaning for all stakeholders, both technical team members and domain experts. In DDD-jargon: one model within one Bounded Context. This is not something new (but can be expressed by talking about the boundaries of the model). There are other models too.


On Thursday, 29 September 2016 05:44:45 UTC+2, Danil Suits wrote:
any command effectively locks down the state of everything entity in the design until the processing is completed.

No, we didn't use any locking: no need for it, as is often the case with web-based applications. All commands and events come in in a strict sequential order. We have no concurrency. Until now no problems with that. If we would encounter any problems with it and if we would have to consider some locking, then we would use optimistic locking (don't commit a change if the last read version was lower than the current version at the time of writing). But there is no need for it in this model at the moment. Say a crewmember is available and assigned to a flight task and immediately after that the crewmember becomes unavailable, then first the assignment is handled and after that the unavailability triggers events so that the assignment is removed. This is very unlikely to happen on exactly the same point. It is not some high traffic planning: crewmembers and the planner are only occasionally adjusting availability and assignments.


On Thursday, 29 September 2016 05:44:45 UTC+2, Danil Suits wrote:
1a) From which it follows that the model (all of it) is the aggregate.

No, an aggregate is a part of the object graph; that is: the part that is needed for the change in state of the system in that specific Use Case. In the case of assignment of crewmembers to flight tasks: only the assignments are changed. If you would add or remove assignments not directly via the CrewmemberToFlightTaskAssignment collection but via the Flight (with addCrewmember() and removeCrewmember() methods in the Flight class as we had before) then the Flight would be the root of that aggregate and the CrewmemberToFlightTaskAssignment-collection would be in that aggregate under the Flight-root. The rest of the entities that are used in the Assigning Use Case, the flight, the tasks of the used balloon, the crewmember, the availability of the crewmember, the qualifications of the crewmember, are only used to query state, not to change it, and so they are not part of the aggregate used in the Use Case.

On Thursday, 29 September 2016 05:44:45 UTC+2, Danil Suits wrote:
2) The design has no explicit aggregate root; at least, not one that I have been able to identify in the design thus far.

In the case of the the AssigningCrewmembersToFlightTasks use case there is only one entity that is changed: we create or delete a CrewmemberToFlightTaskAssignment object. Nothing under it, so this is the one and only entity in the aggregate and hence the "aggregate root" in this use case. In other use cases other entities are changed. I try to keep the depth of aggregates as shallow as possible (also to respect Demeter's Law) so if it is possible to do the change in one entity then my aggregate is just that one entity (and hence the aggregate root).


On Thursday, 29 September 2016 05:44:45 UTC+2, Danil Suits wrote:
3) The definition of the business invariant, which in an OO design would normally be implemented within the entity command methods, are instead given first class status as use cases.

Yes. Because those invariants are not needed outside the use case. I change system state only via use cases. That is why I give them the responsibility to guard the invariants that are needed when changing that specific part of system state. BTW, it is not so very much different from how it is normally done in DDD, for an aggregate are just those parts of the object graph (the entities and their relations) that is needed to effectuate a use case. But you then give the responsability to guard the invariants to one entity (the aggregate root) which is not strictly the responsibility of that entity, because it is only useful within that specific use case.


On Thursday, 29 September 2016 05:44:45 UTC+2, Danil Suits wrote:
4) The entities themselves are data structures.  Which is to say, that they are state

Yes you could say that more or less. However, the entities in my model can do pretty sophistic things, as long as they don't change state, at least not outside a use case. Changing system state is only done within use cases. The (state changing) behaviour of entities is restricted to use cases.


On Thursday, 29 September 2016 05:44:45 UTC+2, Danil Suits wrote: 
It's not all that hard to envision that the arguments being passed into the use cases could be value types

That is not what I do. My commands and events are so called value objects, but in my command handler I get the domain objects that are involved and pass them to the use case. In the Assigning use case: the crewmember, flight and task entities. That is because the use case is part of my domain model and in that model I use the whole objects as much as possible, not just identities, because I have a model with objects, not just identities.

Or did you mean something else than identities with "value objects"? BTW I'm still working on a blog post about entities, value objects classes and types where I will put some question marks at the concepts of  'entities' and 'value objects' in DDD. It takes some time to have some ideas ripened further.
Message has been deleted

Herman Peeren

unread,
Oct 1, 2016, 9:47:04 AM10/1/16
to DDD/CQRS
For some dark reason  an answer I wrote to a reaction this morning was deleted ;-(
I just made a summary of what I wrote and submitted it, but it was deleted again...

This time I made a copy of it before submitting. I removed the link I had (to "transparant persistence") for maybe that is triggering the deletion; maybe we must put the link explicitly in the posting, not under a word?

Third try:


On Thursday, 29 September 2016 21:59:20 UTC+2, Danil Suits wrote:

> No need to explicitly add it to a crew-collection of the flight: when the transaction is committed (= when the Unit of Work is flushed) the database and the collections in various entities are synchronised.

I'm troubled by the fact that the persistence of new entities created by the factory is implicit, rather than explicit.  It seems to be "clever", that is to say, a surprise.  This stuff is hard enough to get right even when we are boring.

I use transparant persistence: the objects in my domain layer don't know anything of persistence. It is not part of the domain model. In the infrastructure layer the persistence is implemented. I use Doctrine ORM. Doctrine's Repository provides all data retrieval and reconstitutes the objects that are used in the domain layer. Doctrine's Entity Manager keeps track of all changes in the domain entities. The Repository makes them "managed", so the Entity Manager can keep track of them and flush them at the end of the transaction. In the command handler (application layer)  I use middleware to wrap the whole handling with a transaction. If all is going well in the use case, the transaction is committed by the Entity Manager.

This works all well, but when creating a new entity in my domain layer the Entity Manager is not automatically informed of that. I have to explicitly make that object "managed". In Doctrine that is done with the persist()-method of the Entity Manager. But the Entity Manager is not part of the domain layer. There are several solutions for that:
  • I could inject the Entity Manager into the domain layer. But then I'd have to put $em->persist($nameOfNewObject) after every "new"-statement. That is poluting the code in the domain layer and when I would change the ORM I might have to change every use of that persist-statement. The persistence is no longer transparant for the domain layer, but visible.
  • I could return all newly created entities from the use case, so the application layer can inform the Entity Manager (call persist($entityName)) that those entities have to be persisted when flushing to the database at the end of a succesful transaction. But there is no reason for the use case and domain layer in general to return anything, so it pollutes the code of the use case.
  • I chose to use a Factory to create new entities. The factory is injected into the domain layer. The interface with the domain layer provides a new object. It also makes that object "managed", but that is of no concern for the domain layer; that is only for the infrastructure.
Reply all
Reply to author
Forward
Message has been deleted
0 new messages