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.
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 ).
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
-----邮件原件-----
发件人: Dan Haywood [mailto:[email protected]]
发送时间: 2013年4月17日 2:15
收件人: dev
主题: Re: 答复: 答复: DataNucleusObjectStore.loadPojo problem
Hi Alain,
I can't completely parse the points you are making here. What would help
this conversation, I think, is some code examples that contrasts one style
against the other.
Could you do that? Then we can have the discussion in more concrete terms.
Cheers
Dan
On 16 April 2013 09:23, 张峰昌 <[email protected]> wrote:
> Hi,Dan
>
> Is it become a too heavy root aggregates If we keep all the
> relationship between root aggregates in domain class model, especially
> the relationship is one To many(that is a collection in root aggregates
domain class )?
>
> The heavy root aggregates will bring us some problem in these aspects:
> 1)、transactional failures. Any relationship change(such as collection
> member add ,remove etc) will cause the root aggregates state change,
> so in a multi user and task environment we will experience
> transactional failure most likely.
> 2)、Performance. Query based on the root aggregates maybe consume more
> memory and database resources. Maybe lazyload is a must in this scene.
>
> Alain
>
> -----邮件原件-----
> 发件人: Dan Haywood [mailto:[email protected]]
> 发送时间: 2013年4月13日 16:06
> 收件人: dev
> 主题: Re: 答复: DataNucleusObjectStore.loadPojo problem
>
> On 12 April 2013 10:06, 张峰昌 <[email protected]> wrote:
>
> > Hi Dan:
> > I have update the isis-objectstore-jdo from 1.0.0 to
> > 1.0.1-SNAPSHOT,and the DataNucleusObjectStore.loadPojo works fine.
> > There are two reasons to use "Application style Identity" for me:.
> > (1)、Domain Root Entity should have their Identity, and we should
> > model the relationship between the Root Entities with refrence to
> > Root Entity's Identity insteadof to Root Entity Object directly.
> > References:
> > Effective Aggregate Design:Part II Making Aggregate work togather
> > Author:Vaughn Vernon
> > https://vaughnvernon.co/?p=139
>
>
>
> Sorry, I don't agree.
>
> This is a style of doing DDD that (in my opinion) over-emphasises the
> role of root aggregates, and underemphasises the role of such things
> as patterns such as the module.
>
> I also detect an emphasis on aggregates with the current hype around
> NoSQL databases. When an aggregate = a JSON doc in MongoDB, then,
> yes, sure, links between aggregates must be done using Ids.
>
> But if using a mature ORM such as JDO (one that supports polymorphic
> relationships through interfaces), there's no reason to do this.
>
>
>
>
> >
> > (2)、Domain Root Entity Identity should not depends on
> > JDOHelper.getObjectId(obj).
> >
> >
> How does it? Especially if you use datastore-defined identity, eg as
> in the ToDoItem example:
>
>
>
> @javax.jdo.annotations.PersistenceCapable(identityType=IdentityType.DA
> TASTOR
> E)
>
> @javax.jdo.annotations.DatastoreIdentity(strategy=javax.jdo.annotation
> s.IdGe
> neratorStrategy.IDENTITY)
> public class ToDoItem { .. }
>
>
> Cheers
> Dan
>
>
>
> > Best Regards
> >
> > Alain
> >
> > -----邮件原件-----
> > 发件人: Dan Haywood [mailto:[email protected]]
> > 发送时间: 2013年4月11日 15:16
> > 收件人: dev
> > 主题: Re: DataNucleusObjectStore.loadPojo problem
> >
> > Hi Alain
> > I suspect your JDO annotations are wrong, but before I go off on a
> > wrong tangent, let me ask: what is your intent for the id? should it
> > be assigned by the DBMS/JDO, or should it be assigned by the
application?
> > Dan
> >
> >
> > On 11 April 2013 03:26, 张峰昌 <[email protected]> wrote:
> >
> > > Hi,Dan:
> > >
> > >
> > >
> > > It seems that there are problem when to recreate an pojo with
> > > DataNucleusObjectStore.loadPojo. The entity I have defined that
> > > use entity itself ID which type is java.lang.Long.It’s work fine
> > > when persist the entity,but will failed when load it to display in UI.
> > >
> > > The failed reason is DataNucleusObjectStore.loadPojo passed an
> > > javax.jdo.identity.LongIdentity typed value into
> > > org.datanucleus.api.jdo.JDOPersistenceManager.getObjectById,but
> > > datanucleus requires Long typed value.
> > >
> > >
> > >
> > >
> > >
> > > Exception statack :
> > >
> > > javax.jdo.JDOFatalInternalException: The key value passed to
> > > construct a SingleFieldIdentity of type
> > > "javax.jdo.identity.LongIdentity" for class
> > > "com.thingray.party.dom.person.Person" is of an incorrect type
> > > ("javax.jdo.identity.LongIdentity") - should be "Long".
> > >
> > > at
> > >
> > > org.datanucleus.api.jdo.NucleusJDOHelper.getJDOExceptionForNucleus
> > > Ex
> > > ce
> > > ption(
> > > NucleusJDOHelper.java:561)
> > >
> > > at
> > >
> > > org.datanucleus.api.jdo.JDOPersistenceManager.newObjectIdInstance(
> > > JD
> > > OP
> > > ersist
> > > enceManager.java:1622)
> > >
> > > at
> > >
> > > org.datanucleus.api.jdo.JDOPersistenceManager.getObjectById(JDOPer
> > > si
> > > st
> > > enceMa
> > > nager.java:1740)
> > >
> > > at
> > >
> > > org.apache.isis.objectstore.jdo.datanucleus.DataNucleusObjectStore
> > > .l
> > > oa
> > > dPojo(
> > > DataNucleusObjectStore.java:390)
> > >
> > > at
> > >
> > > org.apache.isis.objectstore.jdo.datanucleus.persistence.adapterman
> > > ag
> > > er
> > > .DataN
> > > ucleusPojoRecreator.recreatePojo(DataNucleusPojoRecreator.java:38)
> > >
> > > at
> > >
> > > org.apache.isis.core.runtime.persistence.adaptermanager.AdapterMan
> > > ag
> > > er
> > > Defaul
> > > t.adapterFor(AdapterManagerDefault.java:300)
> > >
> > > at
> > >
> > > org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento$
> > > Ty
> > > pe
> > > $2.rec
> > > reateAdapter(ObjectAdapterMemento.java:104)
> > >
> > > at
> > >
> > > org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento$
> > > Ty
> > > pe
> > > .getAd
> > > apter(ObjectAdapterMemento.java:170)
> > >
> > > at
> > >
> > > org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento.
> > > ge
> > > tO
> > > bjectA
> > > dapter(ObjectAdapterMemento.java:288)
> > >
> > > at
> > >
> > > org.apache.isis.viewer.wicket.model.models.EntityModel.load(Entity
> > > Mo
> > > de
> > > l.java
> > > :193)
> > >
> > >
> > >
> > > The entity I have defined:
> > >
> > >
> > >
> > >
> > >
> > > import java.util.ArrayList;
> > >
> > > import java.util.List;
> > >
> > >
> > >
> > > import javax.jdo.annotations.IdGeneratorStrategy;
> > >
> > > import javax.jdo.annotations.IdentityType;
> > >
> > > import javax.jdo.annotations.Inheritance;
> > >
> > > import javax.jdo.annotations.InheritanceStrategy;
> > >
> > > import javax.jdo.annotations.PersistenceCapable;
> > >
> > > import javax.jdo.annotations.Persistent;
> > >
> > > import javax.jdo.annotations.PrimaryKey;
> > >
> > > import javax.jdo.annotations.Queries;
> > >
> > > import javax.jdo.annotations.Query;
> > >
> > > import javax.jdo.annotations.Version;
> > >
> > > import javax.jdo.annotations.VersionStrategy;
> > >
> > >
> > >
> > > import org.apache.isis.applib.annotation.Hidden;
> > >
> > > import org.apache.isis.applib.annotation.MemberOrder;
> > >
> > > import org.apache.isis.applib.annotation.Named;
> > >
> > > import org.apache.isis.applib.annotation.Optional;
> > >
> > > import org.apache.isis.applib.annotation.Title;
> > >
> > > import org.apache.isis.applib.annotation.When;
> > >
> > > import org.apache.isis.applib.value.Date;
> > >
> > >
> > >
> > > import com.thingray.party.dom.Party;
> > >
> > > import com.thingray.party.dom.person.value.GenderType;
> > >
> > > import com.thingray.party.dom.role.value.PartyRoleType;
> > >
> > > /**
> > >
> > > *
> > >
> > > * 人。
> > >
> > > * @author alain
> > >
> > > * @version $Revision 1.0$ 2012-10-15
> > >
> > > * @since 1.0
> > >
> > > * @author (lastest modification by $Author$)
> > >
> > > */
> > >
> > > @PersistenceCapable(table="PA_PERSON")
> > >
> > > @Queries({
> > >
> > > @Query(name = "person_find_by_name",
> > >
> > > value = " SELECT FROM
> > > com.thingray.party.dom.person.Person " +
> > >
> > > " WHERE
> > > firstName.indexOf(:personName)>=0 " +
> > >
> > > " ORDER BY
> > > firstName"),
> > >
> > > @Query(name = "person_find_by_login_account",
> > >
> > > value = " SELECT FROM
> > > com.thingray.party.dom.person.Person " +
> > >
> > > " WHERE
> > > loginAccount==:loginAccount")
> > >
> > > })
> > >
> > > @Inheritance(strategy=InheritanceStrategy.NEW_TABLE)
> > >
> > > @Version(strategy=VersionStrategy.VERSION_NUMBER,column="VERSION")
> > >
> > > public class Person {
> > >
> > > // {{ PartyID (参与方标识)
> > >
> > >
> > >
> > > private Long partyID;
> > >
> > >
> > >
> > > @MemberOrder(sequence = "1")
> > >
> > > @Hidden(when=When.UNTIL_PERSISTED)
> > >
> > > @Named("参与方标识")
> > >
> > > @Persistent(primaryKey = "true", valueStrategy =
> > > IdGeneratorStrategy.NATIVE)
> > >
> > > public Long getPartyID() {
> > >
> > > return partyID;
> > >
> > > }
> > >
> > >
> > >
> > > public void setPartyID(final Long partyID) {
> > >
> > > this.partyID = partyID;
> > >
> > > }
> > >
> > >
> > >
> > > // }}
> > >
> > >
> > >
> > > // {{ LastName (姓)
> > >
> > > private String lastName;
> > >
> > >
> > >
> > > @MemberOrder(sequence = "1")
> > >
> > > @Named("姓")
> > >
> > > @Title(sequence="1")
> > >
> > > public String getLastName() {
> > >
> > > return lastName;
> > >
> > > }
> > >
> > >
> > >
> > > public void setLastName(final String lastName) {
> > >
> > > this.lastName = lastName;
> > >
> > > }
> > >
> > > // }}
> > >
> > >
> > >
> > > // {{ FirstName (名)
> > >
> > > private String firstName;
> > >
> > >
> > >
> > > @MemberOrder(sequence = "2")
> > >
> > > @Named("名")
> > >
> > > @Title(sequence="2")
> > >
> > > public String getFirstName() {
> > >
> > > return firstName;
> > >
> > > }
> > >
> > >
> > >
> > > public void setFirstName(final String firstName) {
> > >
> > > this.firstName = firstName;
> > >
> > > }
> > >
> > > // }}
> > >
> > > }
> > >
> > >
> > >
> > >
> > >
> > > Alain
> > >
> > >
> > >
> > >
> > >
> > >
> >
> >
>
>