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 >
