Thursday, December 24, 2015

Strategic Domain Driven Design. Microservices.

The essence of Strategic Design in DDD is to segregate domain model into well defined areas by business objectives called sub-domains. To some extend, it is Separation of Concerns principle at  enterprise level. Having functionally decomposed domain into separate sub-domains with its own distinct bounded contexts, makes software systems much simpler and more responsive to business changes.

Let's imagine typical situation: we are working on legacy project related to banking area within a code base with no clear boundaries between sub-domains, so everything is tightly coupled, architecture leaning toward "Big bull of mud" which is developed and deployed as monolith application (for ex. as one war-file on Tomcat server). However, project is commercially successful and there is a constant need to grow and scale it, so we are challenged by re-factoring and improving architecture.

Making sense of Bounded Context.

We decided to begin re-factoring from deeper analysis of company core concepts: "Accounting" and transfer "Limits". After series of analysis we came up with idea to separate Account and Limit concerns into distinct sub-domains, so each of them would have its own well defined responsibility within its own module:
  • Accounting sub-domain is responsible for credit/debit operations and keeping balance;   
  • Limit Management sub-domain provides flexible functionality, which allows end-users to define the limit policy for their accounts;
The obvious question that we must be asking to our self is how interconnected should modules be. After all, the entire model has to work as a whole and there needs to be some interconnections even across isolated modules. Let's have a zoomed look on our model and consider the following use case:
When user wants to debit some amount of money from his account, associated limits must be checked before. If the amount exceeds limit then initiated operation must be interrupted and user should be replied with an appropriate message. 
Since Accounting operations depend on Limits (we need to check limits before debit), it looks like "Limits" entity belongs to both sub-domains (please, see Figure 1). Now the challenging question is: How to properly isolate two modules in the case where they are logically coupled?


 Figure 1. The fragments of "Limit Management" and "Accounting" sub-domains. 

The answer lies within business itself! The interesting thing was discovered during discussing "Limit" notion with domain experts. From "Limit Management" point of view, the "Limit" is defined by set of complex rules and policies. For example some of them are:
  • daily debit limit entered by user should not be greater than weekly one, otherwise it makes no sense; 
  • the maximum amount of overdraft limit depends on type of contract, customer profile and  monthly incomes etc.;
  • authorization rules, for ex. call-center agent could change some limits on user behalf.

At the same time from "Accounting" domain point of view, the "Limit" is just a threshold value which we should not exceed when performing credit/debit operations.

It looks like the word "Limit" is overloaded! Obviously, it has a different semantic meaning depending on the context. The same semantic dissonance happens with notion "Account". Within "Accounting" context it is responsible primary for keeping balance and credit/debit operations, whereas within "Limit Management" context, the "Account" is not credit/debit anymore, but rather it is just an unique identifier (number) limit rules are associated with.

Therefore, when it comes to the concrete modeling we also struggle to puck in all of "Limit" aspects into one entity. Indeed, if concept is overloaded then one single entity will be over-complicated too. Wouldn't be much cleaner if we explicitly defined what the "Limit" does mean for every contexts and implemented two distinct models of "Limit" so every one represents proper vision of target context?

The principle of model segregation is very often used in Software world. Think about Thread! In Java context the Thread model has some major methods such as: start(), interrupt(), join() allowing client code to manage parallel execution, anyway it is very tiny in comparing with what Thread is in context of OS. Indeed, the Thread model is much richer here, it encompasses such things as: execution stack, scheduling, counters, registers etc., it also could be Green or Hyper, or even not thread at all but Process. Could you imagine the level of mess if on Java level we were forced to deal with all those "richness" related to OS level?

How decoupling of two domains is not a big problem anymore. Since for "Accounting" sub-domain the "Limit" is just a threshold value (and here we absolutely don't care about other aspects which are specific to "Limit Management"), we don't have to share one Limit entity between two sub-domains. But what we rely need is to map one concept into another (please, see Figure 2).



Figure 2.  The fragments of  Context Mappings.

Let's go deeper into code and have a zoomed look on debit use case. It is a typical "check then act" workflow winch goes across sub-domain boundaries:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.ebank.accounting.app;

public class AccountingApplication {
  

  @Transactional
  public void debitAccount(AccNumber accNumber, Amount amount){

   Limits limits = this.limitService.getLimitsFor(accNumber);

   Statistics statistics = this.transactionsStatisticService.calculateFor(accNumber);

   Account account = this.accountRepository.findBy(accNumber);

   account.debit(amount, limits, statistics);

   this.accountRepository.save(account);
 }
  ...
}


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.ebank.accounting.domain.model;


public class Account {

    public void debit(Amount amount, Limits limits, Statistics statistics){

       if(limits.isDebitAllowed(amount, statistics)){
           doDebit(amount);
       }else{
          throw new ExccededDebitLimitException(amount, limits);
       }

    }
  ...
}


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.ebank.accounting.infrastructure.limits;


public class InternalLimitServiceImpl implements LimitService{
  
  private LimitApplication limitApplication;  

  ...  

  public Limits getLimitsFor(AccNumber accNumber){
      
   LimitsData limitsData = limitApplication.getLimitDataFor(accNumber); 
   
   Limits limits = translate(limitsData);    
   
   return limits;
  }
  ...
}

As you probably may mentioned, the InternalLimitServiceImpl is some kind of adapter, which communicates to "Limit Management" domain and maps foreign concept (LimitsData) into local one (Limits).

Thus, we made a sense of our Bounded Contexts and mapping between them. In his book "Implementing Domain Driven Design", Vaughn Vernon placed Bounded Context into chapter 2, immediately after introduction. Eric Evens said that he would also do that, if he had a chance to rewrite his own book "Domain Driven Design".  Both authors agreed  - Bounded Context is an corner stone of Strategic Design!

A Bounded Context is an explicit boundary within which a domain model exists.... It is often the case that in two explicitly different models, objects with the same or similar names have different meanings. When an explicit boundary is placed around each of the two models individually, the meaning of each concept in each Context is certain...

Some projects fall into the trap of attempting to create an all-inclusive model, one where the goal is to get the entire organization to agree on concepts with names that have only one global meaning. Approaching a modeling effort in this way is a pitfall...

The best position to take is to embrace the fact that differences always exist and apply Bounded Context to separately delineate each domain model where differences are explicit and well understood.
--Vaughn Vernon,"Implementing Domain Driven Design". Chapter 2.

Now it's time to scale at strategic level!

Very often along with separation of concerns, different non-functional requirements could be posed for sub-domains, for instance:
  • Business people might have different level of interest to particular sub-domains, some ones they want evolve and develop, whereas another ones are less susceptible to frequent changes. Usually, "hot areas" are the ones where competitive advantage comes from, and stable ones just supporting first ones. In DDD the first type called Core Domain, the second one - Supporting Domain.
  • Some sub-domains are under heavier load that other ones, so different scaling approaches is needed.
  • Some complex domains require long learning curve before newcomers became productive, so management prefer to avoid shuffling developers between domains. Team per sub-domain is more cost effective.
Taking on account the point above, we have discovered similar requirements for our banking application:
  1. From business point of view "Accounting" sub-domain is pretty stable so once we have implemented credit and debit operations, there are no reasons for frequent changes, whereas with limit concept it's absolutely opposite situation. The limit management is competitive advantage for our company, so business people expect great flexibility there. They are going to concentrate mostly on this area and deliver better and smarter functionality for our users. 
  2. Performance and throughput requirements are different too. On weekends, especially  during shopping time, we expect sharp peak of transaction volumes, thus "Accounting" sub-domain will be under very heavy load, whereas "Limit Management" is always under constant moderate load. 
  3. We want to have separate dedicated teams for "Accounting" and "Limit Management", so the first team will concentrate on deep technical stuff related to performance tuning and optimizations, whereas the second team will be more involved into collaborating with business people for improving functionality.
It looks like, we need shorter development iterations and more frequent releases for "Limit Management" sub-domain. On the other hand, in order to address scalability requirements posed to "Accounting", it would be great if we deployed it into some sort of auto-scaled environment. Therefore, we need to split our monolith application into two smaller parts, where every part would have its own distinct releases frequency and different deployment options.

Since we already have segregated sub-domains and established certain context boundaries, splitting monolith shouldn't be a big deal. We need only to re-implement LimitService interface, so in stead of internal in-memory call, it will make REST call to separately deployed "Limit Management" service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.ebank.accounting.infrastructure.limits;


public class ExternalLimitServiceImpl implements LimitService{
  
  private LimitRestClient limitRestClient;  

  ...  

  public DebitLimits getLimitDataFor(AccNumber accNumber){
      
   Response limitsDataResponse = limitRestClient
   .call(new LimitRequest(accNumber)); 
   
   Limits lmits = parseLimits(limitsDataResponse);    
   
   return limits;
  }
  ...
}

What we have done here? it is an elementary step toward Microservice architecture:

...the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery.
--Martin Fawler, http://martinfowler.com/articles/microservices.html

Autonomy is a crucial ingredient!

Having separated our monolith application into two smaller application (services) and allocated distinct team on each of them, we achieved a great success in solving point 1 and 3 from the requirements list above. Really, now every team could concentrate on their specific issues, without being forced to follow the same agile iteration, release procedures and interfering each others.  But the REST call between "Accounting" and "Limit Management" services (which is needed for checking limit during credit/debit operations) becomes a serious disadvantage:
  • performance and scalability suffers; one extra remote call takes more time to handle credit/debit request as well as puts extra load on "Limit Management" side.
  • reliability significantly decreased; if for some reasons "Limit Management" service is not available then wast majority of "Accounting" functionalities are blocked too;
  • harder to test; in order to perform "black box" functional testing of "Accounting" service  we need to set up some sort of mocked service which will imitate "Limit Management" functionality;
How could we solve this issue?  "Accounting" service needs to pull "Limit Management" service and this is strict dependency! Actually there is one life hack for this sort of issue: "invert pull to push!". Wouldn't it better if instead of pulling "Limit Management" for any single credit/debit request, the "Limit Management" service sends to "Accounting" some sort of notification every time when limits have changed on its side. In turn,  the "Accounting" service consumes this notification and update its own "local copy" of limit. So every time when checking limit is needed the "Accounting" service read it from its local database instead of calling remote "Limit Management" service. Actually, in DDD world the mentioned notification is called Domain Event which is published by "Limit Management" and consumed by "Accounting" sub-domains.

A greater degree of autonomy can be achieved when dependent state is already in place in our local system. Some may think of this as a cache of whole dependent objects, but that’s not usually the case when using DDD. Instead we create local domain objects translated from the foreign model, maintaining only the minimal amount of state needed by the local model.
--Vaughn Vernon,"Implementing Domain Driven Design". Chapter 3. Context Maps.

Thus, the  "Accounting" domain will have it's own "small" limit model with root entity - "Limits". It stores local copy of limits and it is updated every time when LimitWasChangedEvent is consumed by "Accounting" service: 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.ebank.accounting.infrastructure.events

public class DomainEventListener{
    
 private LimitApplication application;
      
  public void handle(LimitWasChangedEvent event){   
     
    application.updateLimit(readAccNumber(event), readLimit(event));

  } 
 ...
} 


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.ebank.accounting.app.limits; 

import com.ebank.accounting.domain.model.limits.Limits;
import com.ebank.accounting.domain.model.limits.Limit;

public class LimitApplication{    
   ...
 
 @Transactional    
 public void updateLimit(AccNumber number, Limit limitRule){
       
   Limits limits = this.limitRepository.findBy(number);
      
   limits.addOrUpdate(limitRule);
   
   this.limitRepository.save(limits);
   
 }   
} 


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.ebank.accounting.app.accounting; 

import com.ebank.accounting.domain.model.Account;
import com.ebank.accounting.domain.model.limits.Limits;

public class AccountingApplication {
  ...

  @Transactional
  public void debitAccount(AccNumber accNumber, Amount amount){

   Limits limits = this.limitRepository.findBy(accNumber);

   Statistics statistics = this.transactionsStatisticService.calculateFor(accNumber);

   Account account = this.accountRepository.findBy(accNumber);

   account.debit(amount, limits, statistics);

   this.accountRepository.save(account);
 }
  ...
}


Microservices is about distribution by business areas.

The Microservices seems to be promising architecture pattern, it might push scalability and resilience of enterprise to the next level as well as speed up development life cycles. But we always should take on consideration design issues. Since Microservices is about distribution by business responsibilities, the key to success lays on Strategic Design. In other words, on understanding of entire enterprise at higher level and ability to functionally decompose domain into Sub-domains and recognize Bounded Contexts where model is applicable.


Tactics without strategy is the noise before defeat. 
--Sun Tzu, "The Art of War" (around 500 B.C)

However, it worth to mention tactical design issues too. As we already seen,  remote calls are slower, much harder to test and are always at risk of failure. Therefore, the services should operate as autonomously as practical. Operations must execute largely independently of surrounding services, which means everyone has to manage eventual consistency. The proper eventual consistency could not be achieved without precise analysis of transaction boundaries, this issue is well known to DDD and addressed by tactical patterns: Aggregate and Domain Event. 


Tuesday, December 15, 2015

Enchance scalability with DDD. Part 2: Domain Event.

Properly identified consistency boundaries allowed us to spit one large aggregate in two smaller ones which in turns promise to scale better. Thus, two operations which modify "logically-independent" data chunks could be performed in parallel. However, very often when it comes to model real domain entities, they are not so "independent", but rather tightly coupled in such a way that modification (or creation/deleting) of one entity requires additional modification to execute on one or more other entities in order to respect domain consistency.

In previous post we have considered simple user management domain (it's highly recommended to read it before). By working with out domain experts Alice and Bob, we already discovered that software should handle simultaneous changing ParenUser's name and adding new ChildUser to the same ParentUser.

5. Multiple user could simultaneously change ParentUser's name and add new ChidlUser to the same ParentUser.

To make things slightly more complicated, let's add new rule:

 6. ParentUser should track number of its ChildUsers.

1
2
3
4
5
6
7
 public class ParentUser extends User{
  ...
   public int getNumberOfChildren(){
     //implementation
   }   
  ...
 } 

In other words, rule 6 means that number of ChildUsers in our system should be consistent with value returned by getNumberOfChildren(). It looks like we have encountered a new consistency rule which spans over ParentUser and ChildUser aggregates.

We already know that for sake of scalability we have to keep aggregates as small as possible and add new entities forming larger aggregates only when  true consistency rule is identified.

So it seams this time we could justify referencing our entities:   

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 public class ParentUser extends User{
   
   private Set<ChildUsers> childUsers;  
 
   public int getNumberOfChildren(){
     return children.size();
   }
   
   public void addChild(ChildUser newChildUser){  
     this.childUsers.add(newChildUser)  
   }   
 
  ...
 } 

In fact, this solution comes us back to violation of domain rule 5! But we don't want to disappoint Alice and Bob again!

What if we keep Disconnected Model solution, but add simple counter to ParentUser and increment it every time when new ChildUser is added to our system.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class ParentUser extends User{
   
   private int childrenCounter;
   ...

   public int getNumberOfChildren(){
     return this.childrenCounter
   } 

   public void incrementChildNumber(){
    this.childrenCounter++;
   } 
  ...
 } 


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class UserApplication{
   ...

   @Transactional
   public void addNewChildUser(Name name, UserId parentId){
     ChildUser child = new ChildUser(name, parentId);
     this.repository.save(child);

     ParentUser parent = repository.find(parentId);
     parent.incrementChildNumber();
     this.repository.save(parent);

   } 
  ...
 } 

The problem of the solution above is - it violates another fundamental rule of modeling aggregate.

Modify only ONE aggregate instance per transaction!

A properly designed Bounded Context modifies only one Aggregate instance per transaction in all cases. What is more, we cannot correctly reason on Aggregate design without applying transactional analysis. 
Limiting modification to one Aggregate instance per transaction may sound overly strict. However, it is a rule of thumb and should be the goal in most cases
--Vaughn Vernon. Implementing Domain-Driven Design

Keeping in mind performance and scalability, our last solution is even worst than large aggregate one. Indeed, it still violates rule 5 and involves more entities into transaction. Actually, modifying multiple aggregates instances in one transaction dramatically limits scalability. For example, if we store each aggregate type in different relational database then we would need distributed transaction. If we store them as documents in NoSQL database (such as mongoDB), then we would get transaction issue again (most of NoSQL solution doesn't support modification of several documents in one transaction).  And even though we store them in the same oracle db, we still are not free of: "ORA:08177 cannot serialize access to this transaction",  because of simple rule: more data involved in transaction - higher chances of  concurrent conflicts.

Just because we are given a use case that requires for maintaining consistency in a single transaction doesn’t mean we should do that. Often, in such cases, the business goal can be achieved with eventual consistency between aggregates. 

Domain Event pattern.

There is a practical solution for achieving eventual consistency in a DDD model. An Aggregate publishes a Domain Event which is in some period of time delivered to one or multiple subscribers.

One point could be added to the tweet above: Language of Reactive systems: Events.  The Event is powerful tool and not only for achieving eventual consistency. Sometimes, for facilitating and accelerating analysis of target domain, DDD experts practice Event Storming technique, which leads to fully behavioral model, with no underlying data model. Focusing primary on behavior is crucial strategy for scalable solution (just remind how we came up with Disconnected Model in previous post), thus Event Storming might also help to shift mind for DDD beginners who use to focus mostly on data models.  

The key thing here is to properly understand what is Domain Event. A domain event captures an occurrence that happened in a domain and what poses interest for domain experts. The domain expert is not interested in databases, web sockets, or design patterns, but in the business domain of the things that have to happen. Domain events capture those facts in a way that doesn't specify a particular implementation. 


Let introduce new Domain Event into our possible implementation:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class UserApplication{
   ...

   @Transactional
   public void addNewChildUser(Name name, UserId parentId){
     ChildUser child = new ChildUser(name, parentId);
     this.repository.save(child);

     ChildUserWasCreatedEvent event = new ChildUserWasCreatedEvent(child);  
     this.eventPublisher.publish(event);

   } 
  ...
 } 

Please, pay attention modifying aggregate and publishing event should be transactional, so once aggregate changes are persisted and corresponding event is published, an underlying  messaging infrastructure should guarantee delivery to a consumer.  Event name is always in past tense, since it represent a fact that already happened in our domain.

There may be one or more consumers of our domain event, which consume it asynchronously. The task of consumer is to execute business rule making domain model eventually consistent. If event processing is failed, an appropriate re-delivery strategy must be provided.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class DomainEventListener{
   ...

   @Transactional
   public void handle(ChildUserWasCreatedEvent event){
     ParentUser parent = repository.find(event.getParentId());
     
     parent.incrementChildNumber();
     
     this.repository.save(parent);     

   } 
  ...
 } 

If we store aggregate events permanently in an event log, then we might notice that we could reconstruct aggregate state by applying all stored events on given aggregate in chronological order, thus entire aggregate state could be stored as its event log. This technique called Event Sourcing. Despite the obvious performance hit of fetching such aggregate from database, it gives enormous possibilities for business, especially for analytics and data mining. Along with CQRS this architecture style is well known and adopted in DDD community (for getting more info please see Greg Young talk).
 
The Event pattern is inevitable artifact of distributed high scalable systems, once we get the hang of using it we will be addicted and wonder  how we survive without it until now. It worth to mention that the biggest challenge here is to come up with appropriate architecture and to chose underground infrastructure. The range of issues and possible solution is too large for blog format, so for getting deeper insights, I would recommend to read Vaughn Vernon's Implementing Domain Driven Design, chapters 4 and 8.   



Sunday, December 6, 2015

Enchance scalability with DDD. Part 1: Effective aggregate.

Modelling relation between entities in object oriented way may at first seem as a quick and relatively easy work especially when developers empowered with ORM tool such as hibernate. But among all others design flaws, this one very often leads to poor performance and scalability bottlenecks. The best way to explain why? by simple and straightforward example, which would allow reader to grasp general idea without being overloaded with proprietary domain knowledge. In this post I'm going to touch only modelling basics, so experienced modeler wouldn't find it much helpful.    

Let's consider user management domain which is described by the following rules:

1. Every user has a name. The Name has length from 5 to 15 characters;

2. Name is changeable and could be updated many times;  

3. The User could be of two types: Parent or Child. Unlike Child, Parent user could have zero or many Children. Please see Figure 1. 

4. The model should allow to add Child users to Parent one.


  Figure 1: "one-to-many" association between Parent and Child users.


For warming up, let have a look at first two rules, where our task is to model an User entity with a name constrained by max and min characters.  The name could be represented by primitive String type: 
1
2
3
4
5
6
7
public class User {  
  private String name;  
  ...  
  public void changeName(String name){  
    this.name = name;  
  }   
 .... 

Actually, it's more preferable to "wrap" String into immutable Value Object, let call it Name:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 public class Name {  
   private final String name;  

   public Name(String name){
    validateLength(name) 
    this.name = name; 
 }    
  ....   
 // validation logic, equal, hash code 
 }  


Such modelling option is: 
  • explicitly represents domain concept. So it's more readable and easier to reason about model; 
  • aligned with Single Responsibility Principle. The User class is not cluttered with name validation logic;
  • aligned with Open-Close Principle. For ex. if we want to extend name by adding first, last names, then we would have to modify only Name class, whereas User class is remained untouched;
For getting more info, please see this awesome talk by Dan Bergh Johnsson, where he provided more considerations about usage of Value Object pattern instead of primitive data types.

In order to model last two domain rules, let's define all common functionality (including name) in a base abstract class and implement "one-to-many" relationship in concrete Parent and Child classes. The hibernate solution with hierarchy mapped by single table and discriminant is below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Entity
@Table(name = "users")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn( name="usertype",  discriminatorType=DiscriminatorType.STRING)
public abstract class User {
    @EmbeddedId 
    private UserId id;    

    private Name name;
    @Version 
    private int version;

    public void changeName(Name name){
        this.name = name;
    }
 ...
}



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 @Entity  
 @Table(name = "users")  
 @DiscriminatorValue("P")  
 public class ParentUser extends User{
  
   @OneToMany(fetch = FetchType.LAZY, mappedBy = "parent")  
   private Set<ChildUser> childUsers = new HashSet<ChildUser>();  
  ...  
   public void addChild(ChildUser newChildUser){  
     this.childUsers.add(newChildUser)  
   }  
...
 } 


1
2
3
4
5
6
7
8
9
 @Entity  
 @Table(name = "users")  
 @DiscriminatorValue("C")  
 public class ChildUser extends User{  
   @ManyToOne   
   @JoinColumn(name = "parent_id")  
   private ParentUser parent;  
  ...  
 }


1
2
3
4
5
 @Embeddable  
 public class UserId{    
  private String Id;  
  ...  
 }

 
Given implementation works well until Alice and Bob ran into issue.
Who are Alice and Bob? 
They are unlucky users of our model who suffer from its limited scalability.  Let's imagine: Bob fetches copy of ParentUser object from database and tries to change the name (please, see Figure 2). At the same time T1, Alice also fetches copy of same ParentUser object and tries to add new ChildUser to it. Few seconds later T2, Bob have finished its operation and committed transaction, whereas Alice is still in progress. Finally Alice managed to finish her job and tries to commit its own transaction T3, but poor Alice got OptimisticLockingException with some message like: “ParentUser had been modified by Bob, while you was working on it! Please, re-load ParentUser and do your operation again.” 

Figure 2: Failed concurrent modification of the same user.

It is likely that following dialog may took place:
Alice: Bob, what did you do with that ParentUser?
Bob: I corrected misspelling in the name!
Alice: Changing a name should NOT affect adding new ChildUser!
The Alice’s conclusion is domain knowledge which we missed to properly model. Instead we modeled an object graph which include two entities: ParentUser and ChildUser.
In his book Domain Driven Design, Eric Evans called this graph an Aggregate. The aggregate is a cluster of associated objects that we treat as a unit for the purpose of data changes.


Figure 3: Large cluster aggregate.


How to address this issue? 

Modifying a name and adding a new ChildUser belong to same user concept but they are not affecting each others. In object-oriented words: they are not forming the same invariant. Normally, in enterprise application development we consider the invariant as a business rule that must always be consistent. Since both operations have different consistency boundaries, they could be performed independently. 

Eric Evans emphasized that complex association between objects always comes with a cost: 

“It is difficult to guarantee the consistency of changes to objects in a model with complex associations. Invariants need to be maintained that apply to closely related groups of objects, not just discrete objects. Yet cautious locking schemes cause multiple users to interfere pointlessly with each other and make a system unusable.”

--Eric Evans. Domain Driven Design
Vaughn Vernon suggests to start from smallest possible aggregates and associate new entities only if true invariant involving them is identified:
Rule: Design Small Aggregates:
What additional cost would there be for keeping the large-cluster Aggregate? Even if we guarantee that every transaction would succeed, a large cluster still limits performance and scalability.
Smaller Aggregates not only perform and scale better, they are also biased toward transactional success, meaning that conflicts preventing a commit are rare. This makes a system more usable. Your domain will not often have true invariant constraints that force you into large-composition design situations. Therefore, it is just plain smart to limit Aggregate size. When you occasionally encounter a true consistency rule, add another few Entities, or possibly a collection, as necessary, but continue to push yourself to keep the overall size as small as possible.
--Vaughn Vernon. Implementing Domain-Driven Design


Personally, I found Vaughn Vernon’s book the most practical guide for getting started with DDD. The examples he provided are very simple and not overloaded with complex domain knowledge, so reader could very quickly pick up concepts and apply them on his daily work.


Second attempt! Domain driven solution. 

Getting deeper into domain we’ve discovered that two operations: changing a name and adding new Child user don’t form the same invariant and they have different consistency boundaries.  Both the referencing Entity ParentUser and referenced entity ChildUser must not be modified in the same transaction. Only one or another may be modified in a single translation. Thus, they should not be guarded by the same concurrency control mechanism, in our case it’s optimistic version control implemented by version counter. On the other hand ParentUser and ChildUser entities are logically connected and somehow we have to keep tracking of this fact.  How we can model such dualistic nature?  

Now consider an alternative model on Figure 4 and it's implementation below. This time PersonUser entity doesn’t have object reference to set of ChildUsers anymore, from the other side the ChildUser is also free of reference to its ParentUser object, instead it has simple property - parent id, which logically relates two entities. Thus, having totally removed bidirectional object-oriented references between two entities, we got two separate aggregates where everyone encompasses only one entity.



Figure 4. ParentUser and ChildUser are modeled as separate aggregates.


1
2
3
4
5
6
7
8
@Entity
@Table(name = "users")
@DiscriminatorValue("P")
public class ParentUser extends User {

 // removed set of ChildUsers 

}



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Entity
@Table(name = "users")
@DiscriminatorValue("C")
public class ChildUser extends User{  
    // removed ParentUser

   @Embedded
   private UserId parentUserId;  
   // added simple plain property parentUserId
  
  private ChildUser(Name name, UserId parenUserId){    
    super.name = name;
    this.parentUserId = parenUserId;
  } 
...
}

This technique is called Disconnected Domain Model, and it's actually a form of lazy loading. We referenced ChildUser to ParentUser only by its globally unique identity UserId, not by holding the direct object reference.  

With given model Bob and Alice could perform their operations simultaneously:
Figure 5: Parallel modification of the same user. 

So, we've solved the transaction failure issue by modeling it away. Any ParentUser and ChildUser instances could be manipulated by simultaneous user requests. That's pretty simple! 

Since our aggregates don't use direct references to each other, their persistent state could be moved around to reach large scale. For example, let imagine the domain with relatively small number of ParentUser and infinitely growing amount of ChildUser, then it's obvious that we would like to use some NoSQL solution which supports distributed storage with different scaling option for each entity. Thus, reference by identity plays an important role when it comes to large scale, especially in NoSQL world. It's really crucial to consider AGGREGATE as consistency boundaries and not be driven by a desire to design object graph.

What if we need to read bigger chunk of data?
Very often we need to fetch a data across multiple aggregates, in other words we require  more data than single aggregate type could provide. For example, let assume for read purposes we have to fetch and return ParentUser together with all of its ChildUsers. Should we come back to initial model with larger aggregate?
Not necessarily! This is exactly the case when Martin Fowler’s Data Transfer Object pattern could help: 

The solution is to create a Data Transfer Object that can hold all the data for the call. It needs to be serializable to go across the connection. Usually an assembler is used on the server side to transfer data between the DTO and any domain objects.
--Martin Fowler. Patterns of Enterprise Application Architecture. 
In our case the mapping between domain and DTOs is not “flat one-to-one”. But actually it shouldn't be! Moreover, in his example Martin Fowler provides exactly “not flat” mapping. In addition, I would also say that good justification for applying such costly pattern is actually overcoming incompatibility between domain (behavioral-centric) and read (representation-centric) models.  Sometimes, in complex domains the dissonance between two models could cause so much overheads, that it worth to consider applying CQRS architecture.