At the last DDD Europe conference, a lot of attention was given to "connascence". Especially in a workshop of the ever great Paul Rayner that was all about the term.
I hadn't heard of it yet (<shame>), but it turns out it's the same as what I always called "coupling". While "coupling" is the degree of dependencies between modules, "connascence" focuses on the need to change one component if another changes.
Now, there's 9 different kinds of connascence and they're all listed on wikipedia. However, for me, there's also a link to why we like Domain Driven Design.
The whole idea of DDD is to design software by capturing the language of the business and keeping all business rules in the domain model. At the same time we avoid application logic to sneak in the domain model or business logic to sneak out of the domain.
The reason why we do all this effort is that we want to be connascent to the business domain. When the business changes, we want our model to change as well. If they're not connascent, it means that we modelled the wrong thing.
We don't want our domain model to be connascent to the requirements gathered in a document by a functional analyst and handed over to us.
We don't want our domain model to be connascent to a list of features added to a backlog by a product owner in a scrum process.
We definitely don't want our domain model to be connascent to a UI mockup.
The UI changes all the time. Requirements change all the time. Product owners change their mind constantly. Instead, business rules don't change that often and when they do, we want to change our domain model with it.
Greetings
Jan
Showing posts with label ddd. Show all posts
Showing posts with label ddd. Show all posts
Monday, February 10, 2020
Sunday, September 22, 2013
... about what ubiquitous language we should speak
DDD talks about the use of a ubiquitous language; a common language spoken by the domain experts, functional analysts and developers. As a consequence, the code that is developed should also be written in the same language, using the nouns and verbs existing in the ubiquitous language.

So, lets start an evolution. When we're dealing with a local company that uses Dutch as the language, we use dutch verbs and nouns and they end up in our code.
When we're dealing with an international company, we would use English as the ubiquitous language and we would create an entity called Airplane containing methods like liftOff or makeEmergencyLanding.
Talking to domain experts in a language that is not yours and is not theirs is not that hard, but you tend to miss a lot of the small details and subtleties hidden in the language. You could"google-translate" to-from Dutch-English, but most of the time you end up with verbs like "assignSomthing", "updateSomething" or the dreaded "setSomething". It's poor, plain English, spoken by all those foreigners that aren't living in the US or aren't part of the British Commonwealth. However, most of the time it's the best we can do.
To make a short story long: English speaking developers have a head start on others when capturing the ubiquitous language.
Greetings...
Jan
The code is the language. The language is the code. Hallelujah!
I live in Belgium.
Belgium is small. When you choose the right time of the day, you can drive all the way through Belgium in 2 or 3 hours, even without breaking any speed limits.
The upper part speaks Dutch, the lower French. My native tongue is Dutch. My English skills are OK, but could be better. In France, I could order a meal and buy a croissant, but I wouldn't say my French is good.
The domain experts we work with speak Dutch, French or English. The clients we work for, are local companies or international ones.
All our code is written in English. It's the universal (!= ubiquitous) language. However when talking to our domain experts we speak the language they speak. That is Dutch, French or English. So every time we talk to the business, we take the verbs and nouns that is used in our code and translate it into the language they speak.
That's not very ubiquitous, is it?
We would have an entity like Vliegmachien that is persisted in a VliegmachienRepository.
The Vliegmachien entity would contain method names like stijgOp or maakNoodlanding and all would be fine.
Note that there's no reason to use Dutch terms for technical stuff like Service or Repository . That wouldn't serve any purpose. They can remain in English
Case closed! Everybody happy.
OK, here comes the trouble:
Sometimes we have a Dutch speaking domain expert that leaves the company and is replaced by a French speaking domain expert.
Or this one:
the project is now in maintenance mode and it's not handled by the original Dutch speaking team anymore. Maintenance is now done by someone living in Ukraine.
For these reasons I see no other possibility than to use English as the ubiquitous language. Unless you can foresee that your customer and your team will always be speaking the same language.
Talking to domain experts in a language that is not yours and is not theirs is not that hard, but you tend to miss a lot of the small details and subtleties hidden in the language. You could"google-translate" to-from Dutch-English, but most of the time you end up with verbs like "assignSomthing", "updateSomething" or the dreaded "setSomething". It's poor, plain English, spoken by all those foreigners that aren't living in the US or aren't part of the British Commonwealth. However, most of the time it's the best we can do.
To make a short story long: English speaking developers have a head start on others when capturing the ubiquitous language.
Greetings...
Jan
Tuesday, April 30, 2013
... about using optimistic locking with DDD
One of the main objectives when designing aggregate roots is to reduce concurrent modification from the same aggregate root by 2 users.
Take the example of Vaughn Vernon's aggregate root design essay:
| Taken from http://dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_3.pdf by @VaughnVernon |
In there he writes about the design of an agile project management application where a BackLogItem contains a collection of Tasks each containing a collection of EstimatedLogEntries. When the hoursRemaining on the last open task becomes 0, the status field on the BackLogItem should become DONE. A relational database is used for persistence and hiberante as the ORM.
Lets see what happens when a Alberto would change a baklogitem in complete isolation. The backLogItem contains a number of tasks but only taskX and taskY have remaining hours left.
When Alberto sets the hoursRemaining of task X to 0 the following might happen:
- Alberto sends a SetHoursRemainingOfTaskXToZero command to the application service
- a transaction is started, the application service loads the backLogItem and calls backLogItem.hoursRemaining(taskX, 0)
- the backLogItem delegates the call to taskX . TaskX sets the remaining hours to 0. Finally the backLogItem checks if there are other tasks that have hoursRemaining. There still is some work left in taskY so the status of the backLogItem is kept as is.
- the application service method ends. Hibernate notices taskX's hoursRemaining is changed. The changes are flushed to the db and the transaction commits.
Now, what happens when Alberto sets the hoursRemaining of taskX to 0 and Yves does the same with taskY, both at the same time? The following might happen:
- Alberto sends a SetHoursRemainingOfTaskXToZero command to the application service
- a transaction is started, the application service loads the backLogItem and calls backLogItem.hoursRemaining(taskX, 0)
- Yves sends a SetHoursRemainingOfTaskYToZero command to the application service
- a transaction is started, the application service loads the backLogItem and calls backLogItem.hoursRemaining(taskY, 0)
- the backLogItem that Alberto is working on delegates the call to taskX . TaskX sets the remaining hours to 0. Finally the backLogItem checks if there are other tasks that have hoursRemaining. There still is some work left in taskY so the status of the backLogItem is kept as is.
- the backLogItem that Yves is working on delegates the call to taskY. TaskY sets the remaining hours to 0. Finally the backLogItem checks if there are other tasks that have hoursRemaining. There still is some work left in taskX so the status of the backLogItem is kept as is.
- the application service method on the thread that executes the command sent from Alberto ends. Hibernate notices taskX's hoursRemaining is changed. The changes are flushed to the db and the transaction commits.
- the application service method on the thread that executes the command sent from Yves ends. Hibernate notices taskY's hoursRemaining is changed. The changes are flushed to the db and the transaction commits.
When the backLogItem would be loaded again, it would not contain any hours left, but still the status would not be set to 'DONE'.
To improve this we need locks. When Alberto is busy changing the backLogItem, we need to make sure no-one else can change it. We might be in a pessimistic mood, but most of the time we would favor optimistic locking because that would increase throughput.
Hibernate support optimistic locking by adding an extra column on the table mapped as an number field annotated with @Version. The field should be used by Hibernate only and increases every time someone changes the entity. During flush time Hibernate checks the version with the one currently stored in the database and throws an exception when it notices someone else increased it in the meantime.
So we can add an extra optLock fields on BackLogItem, Task and on EstimatedLogEntries. This way Hibernate will protect us from concurrent changes. Lets check this by replaying our previous scenario:
- Alberto sends a SetHoursRemainingOfTaskXToZero command to the application service
- a transaction is started, the application service loads the backLogItem and calls backLogItem.hoursRemaining(taskX, 0)
- Yves sends a SetHoursRemainingOfTaskYToZero command to the application service
- a transaction is started, the application service loads the backLogItem and calls backLogItem.hoursRemaining(taskY, 0)
- the backLogItem that Alberto is working on delegates the call to taskX . TaskX sets the remaining hours to 0. Finally the backLogItem checks if there are other tasks that have hoursRemaining. There still is some work left in taskY so the status of the backLogItem is kept as is.
- the backLogItem that Yves is working on delegates the call to taskY. TaskY sets the remaining hours to 0. Finally the backLogItem checks if there are other tasks that have hoursRemaining. There still is some work left in taskX so the status of the backLogItem is kept as is.
- the application service method on the thread that executes the command sent from Alberto ends. Hibernate notices taskX's hoursRemaining is changed. Because taskX has become dirty, hibernate increases the optimistic lock field. It compares the optimistic lock field in the db and continues because that one did not change in the meantime. The changes are flushed to the db and the transaction commits.
- the application service method on the thread that executes the command sent from Yves ends. Hibernate notices taskY's hoursRemaining is changed. Because taskY has become dirty, hibernate increases the optimistic lock field. It compares the optimistic lock field in the db and continues because that one did not change in the meantime. The changes are flushed to the db and the transaction commits.
When the backLogItem would be loaded again, it would not contain any hours left, but still the status would not be set to 'DONE'.
Why didn't hibernate protect us from concurrent modification?
This is because Alberto modified taskX while Yves modified taskY. Hibernate doesn't know that both belong to the same aggregate root. TaskY is totally unaware of modifications happening in taksX. It doesn't event know that taskY exists.
The only one that does know is the backLogItem. By changing taskX, we not only want to protect taskX from concurrent modification, but also everything inside the aggregate root that holds taskX.
So how do we solve this?
- When working with aggregate roots, we need to make sure we place the optimistic lock field annotated with @Version field on the aggregate root.
- Second we need to make sure we mark the aggregate root as dirty for every change happening inside the boundaries of the aggregate root. This can be a timestamp or you might just reuse the optLock field and increase it by one.
Replaying our scenario with the BackLogItem marking the AR dirty would result in the following:
- Alberto sends a SetHoursRemainingOfTaskXToZero command to the application service
- a transaction is started, the application service loads the backLogItem and calls backLogItem.hoursRemaining(taskX, 0)
- Yves sends a SetHoursRemainingOfTaskYToZero command to the application service
- a transaction is started, the application service loads the backLogItem and calls backLogItem.hoursRemaining(taskY, 0)
- the backLogItem that Alberto is working on delegates the call to taskX . TaskX sets the remaining hours to 0 and notifies the caller that it's changed. This can be as simple as returning a boolean. The backLogItem now knows one of its tasks have changed. As a result it dirties itself. Finally the backLogItem checks if there are other tasks that have hoursRemaining. There still is some work left in taskY so the status of the backLogItem is kept as is.
- the copy of the backLogItem that Yves is working on delegates the call to taskY. TaskY sets the remaining hours to 0 and notifies the caller that it's changed. The backLogItem dirties itself. Finally the backLogItem checks if there are other tasks that have hoursRemaining. There still is some work left in taskX so the status of the backLogItem is kept as is.
- the application service method on the thread that executes the command sent from Alberto ends. Hibernate notices taskX's hoursRemaining is changed. It also notices that the backLogItem is changed and increases the optimistic lock field. It compares the optimistic lock field in the db and continues because that one did not change in the meantime. The changes are flushed to the db and the transaction commits.
- the application service method on the thread that executes the command sent from Yves ends. Hibernate notices taskY's hoursRemaining is changed. It also notices that the backLogItem is changed and increases the optimistic lock field. It compares the optimistic lock field of the backLogItem to the one stored in the db and notices the latter changed. As a result it throws an exception and the transaction is rolled back.
By placing the optimistic lock field on the aggregate root and by explicitly marking the aggregate root as dirty when a change occurs in one of its containing entities, you can effectively guard your aggregate roots against concurrent modification.
Greetings...
Jan
Subscribe to:
Posts (Atom)


