Hi Alain,
Thanks for taking the time to do this.  Comments within.

Vaughn... this thread relates to some advice in one of your articles.  I've
cc'ed you in case you want to commentate; as you'll see, I don't believe it
makes sense in the case of using an ORM (as Isis does).



On 18 April 2013 07:27, 张峰昌 <[email protected]> wrote:

> Hi Dan:
>
> I have tried build two style domain model with scrum management application
> as business background, and the points I have made are more clear than
> before.
> Domain Concept include:Product、BacklogItem、Release、Sprint、Task etc. A
> product have many BacklogItems、Releases、Sprints.
>


> 1、Large business boundary is #1 reason that make A heavy style root
> aggregate. It's Business boundary makes root aggregate heavy or smaller
> instead of IdentityType(Datastore or Application).
>  For example Product as the only one root aggregate.Product is so heavy if
> we model Product as the only root aggregate,because it needs many
> collections such BacklogItems、Sprints、Releases.
>


Agreed, we should disregard this. The interesting bit of the discussion is
the next one.



> 2、Direct root aggregate Object Referenced maybe the #2 reason that make A
> heavy root aggregate.
>  For example,Product、BacklogItem、Release、Sprint are all root
> aggregate.The relationships between root aggregates have two styles:
>  (1)、Direct root aggregate Object Referenced.  BacklogItem, Release and
> Sprint will keep a property Product(type:Product) object reference to
> represent the relationship with Product(root aggregate)
>  (2)、Root aggregate ID Referenced. BacklogItem,Release and Sprint will keep
> a property ProductID(type:Long) reference to represent the relationship
> with
> Product(root aggregate).This technique is called disconnected domain model
> in Vaughn Vernon's paper(https://vaughnvernon.co/?p=139 ).
>


The PDF of this paper being:[1]

The example code sample that you give below correctly explains Vaughn's
advice, but I don't agree with it.

For the benefit of others reading this thread, Vaughn says:

*Prefer references to external aggregates only by their globally unique
identifier, not by holding a direct object reference (pointer)*

The reason for doing this, he says, is:

*Aggregates with inferred object references are thus automatically smaller
because references are never eagerly loaded.*

and he also says:

*...the disconnected domain model is actually a form of lazy loading...*
*
*
which he then says provides:

*... the additional benefits [of] smaller aggregates better performing
models, scalabiliity and distribution.*

As I say, I don't agree with this.

Vaughn's advice would make sense if we were using Java serialization, but
we're not; we use JDO.  If you look at how JDO works, behind the scenes it
holds an id, and the reference is loaded lazily.  This page [2] explains
the mechanics.

So, in terms of the state, the object is the same size; it holds an Id.

As for scalability, well, (as Maurizio has demonstrated), JDO can be used
on GAE whose implementation is a scalable NoSQL store.  So we have that
covered.

As I see it, this disconnected domain model pattern arises from using
inadequate  non-ORM persistence mechanisms.  But it doesn't make sense if
using an ORM.  Using it we end up with a much more inconvenient model to
work with, for no benefit.

I know Vaughn from the DDD mailing list community, so I've cc'ed him on
this for his reaction if he wishes.

Cheers
Dan

[1]
http://dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_2.pdf
[2] http://db.apache.org/jdo/enhancement.html



> Code Sample for  (1)、Direct root aggregate Object Referenced.
> Product.java
> @PersistenceCapable(identityType=IdentityType.DATASTORE)
> public class Product {
>         // {{ Description (property)
>         private String description;
>
>         @MemberOrder(sequence = "1")
>         @Named("description")
>         public String getDescription() {
>                 return description;
>         }
>
>         public void setDescription(final String description) {
>                 this.description = description;
>         }
>         // }}
> }
>
> BacklogItem.java
> @PersistenceCapable(identityType=IdentityType.DATASTORE)
> public class BacklogItem {
>         // {{ Description (property)
>         private String description;
>
>         @MemberOrder(sequence = "1")
>         public String getDescription() {
>                 return description;
>         }
>
>         public void setDescription(final String description) {
>                 this.description = description;
>         }
>         // }}
>
>         // {{ Product (property)
>         private Product product;
>
>         @MemberOrder(sequence = "2")
>         public Product getProduct() {
>                 return product;
>         }
>
>         public void setProduct(final Product product) {
>                 this.product = product;
>         }
> ......
> }
>
>
> Code Sample for (2)、Root aggregate ID Referenced.
> Product.java
> @PersistenceCapable(identityType=IdentityType.APPLICATION)
> public class Product {
>         // {{ ProductID (property)
>
> @Persistent(primaryKey="true",valueStrategy=IdGeneratorStrategy.NATIVE)
>         private Long productID;
>
>         @MemberOrder(sequence = "1")
>         @Hidden(when=When.UNTIL_PERSISTED)
>         public Long getProductID() {
>                 return productID;
>         }
>
>         public void setProductID(final Long productID) {
>                 this.productID = productID;
>         }
>         // }}
>
>         // {{ Description (property)
>         private String description;
>
>         @MemberOrder(sequence = "1")
>         public String getDescription() {
>                 return description;
>         }
>
>         public void setDescription(final String description) {
>                 this.description = description;
>         }
>         // }}
> }
>
> BacklogItem.java
> @PersistenceCapable(identityType=IdentityType.APPLICATION)
> public class BacklogItem {
>         // {{ BacklogItemID (property)
>
> @Persistent(primaryKey="true",valueStrategy=IdGeneratorStrategy.NATIVE)
>         private Long backlogItemID;
>
>         @MemberOrder(sequence = "1")
>         @Hidden(when=When.UNTIL_PERSISTED)
>         public Long getBacklogItemID() {
>                 return backlogItemID;
>         }
>
>         public void setBacklogItemID(final Long backlogItemID) {
>                 this.backlogItemID = backlogItemID;
>         }
>         // }}
>
>         // {{ Description (property)
>         private String description;
>
>         @MemberOrder(sequence = "2")
>         public String getDescription() {
>                 return description;
>         }
>
>         public void setDescription(final String description) {
>                 this.description = description;
>         }
>         // }}
>
>         // {{ ProductID (property)
>         // this way I means smaller root aggregate,because the current root
> aggregate do not depends on other root aggregate.
>         private Long productID;
>
>         @MemberOrder(sequence = "3")
>         public Long getProductID() {
>                 return productID;
>         }
>
>         public void setProductID(final Long productID) {
>                 this.productID = productID;
>         }
>         // }}
> .........
> }
>
> Best Regards
>
> Alain
>

Reply via email to