文章目录
DDD perspective
Persistence Ignorance
Persistence Ignorance holds that you should separate your domain model from the underlying persistence storage. Classes that you use for modeling your business domain shouldn’t be impacted by how they are stored in the database. Ideally, their design should be as close as possible to the design needed to solve the problem at hand, without taking into consideration persistence concerns.
Value Object
Entity
Value Object vs. Entity
1、相等性比较:
entity 比较唯一标识符字段的值;—— identity equality,同一个对象
value object 比较结构中所有字段的值,没有唯一标识符。—— structural equality,一组相同值的组合
2、历史记录:
entity 因为有唯一标识字段id,所以有该id对象的值变化记录了该entity的历史变化;
value object 没有唯一标识符,所有没有历史记录。
3、独立存在:
entity 指向一个真实的逻辑对象,可以独立存在;
value object 只是一组值的记录,需要作为 entity 的成员对象才有存在意义。
4、不可变性:
entity 可变;
value object 不可变。若value object 值变化,由相等性比较原则,得与变化前的value object 不再是同一个object,所以具有不可变性。
5、数据库表对象:
entity 结构对应于数据库表的表结构;(只要唯一标识符相等就是同一个entity)
value object 不具有实体意义,不能对应于数据库表。(只要值相等就是同一个value object)
6、由于value object 的不可变性,应该总是优先选择value object .
Entity Framework vs. NHibernate
ORM对EF中在域模型(domain model)中使用值对象(value object)的两个限制:
1、实体中的值对象属性不能为null(entity 中的 value object 不能为null)
2、值对象不能引用实体(value object 不能指向entity,因为缺少entity中的唯一标识字段)
这些限制导致无法构建隔离域模型。NHibernate没有这样的限制
举例说明限制内容:
如下有一个实体类Customer,包含了一个值对象Address
public class Customer // Entity
{
public int Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address // Value Object
{
public string Street { get; set; }
public string City { get; set; }
}
1、若需要标识地址不存在,按照限制一:address值对象不可以为null,只能通过时address对象的各字段值为null表示
public class Customer
{
public Customer(int id, string name)
: this()
{
Id = id;
Name = name;
Address = null; // Can’t do that.必须要像下面的方法,使address中的各字段值为空
}
public Customer(int id, string name)
: this()
{
Id = id;
Name = name;
Address = new Address(null, null, null);
}
}
2、使用如下的定义来表示一个Customer实体是不可以的。因为值对象不能引用实体。
public class Customer // Entity
{
public int Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address // Value Object
{
public string Street { get; set; }
public string City { get; set; }
public Country Country { get; set; } // Not supported. 值对象中不可引用实体类对象
}
public class Country // Entity
{
public int Id { get; protected set; }
public string Name { get; protected set; }
}
detached mode
domain model
DTO vs Value Object vs POCO
Having the domain model separated from the persistence model
分类
1、DTO——Data Transfer Object
没有逻辑概念的数据传输对象,用于在一个应用的不同层次或不同应用之间的数据传输。
2、Value Object
具有逻辑概念的数据对象,不具有唯一标识符,作为域模型的组成元素,常作为域实体的属性成员体,不用于数据传输。
3、POJO——Plain Old Java Object, also known as POCO in .Net(Plain Old CLR Object)
区别于 类似于JavaBean这种重型企业Java对象,POJO用于创建于与执行环境无关的域模型,大大降低了表关系的复杂度(因为JavaBean是与执行环境相关的)。
换句话说,Value Object和DTO不应该继承任何重量级的企业组件,因此它们是POCO。 同时,POCO是一个更广泛的集合:它可以是值对象,实体,DTO或您可能创建的任何其他类,只要它不会继承您域中的意外复杂性。
持久层、表现层是否应该公用一个域模型的比较
持久层:persistence layer
表现层:presentation layer、business layer
域模型:domain model
域实体:domain entity
持久模型:persistence model
1、共用一个域模型:
优点:
因为不需要进行“用于接收表现层数据的对象”与“用于持久层的实体类对象”之间的转换,所以效率会高一点。
缺点:
1、冗余包
该类的定义会变复杂,同时包含了两个层次需要用到的包,使用时会显得冗余 ——如果您的实体被“传递”到您的表示层,那么该层需要“了解”您在持久层中使用的特定库/框架。如果你正在做web 的东西,这不是什么大问题,但如果你做rich clients(例如Swing)那么可能你的客户需要随身携带额外包袱)
2、冗余字段
若实体类Entity与传输对象DTO不是一对一的关系,比如表现层/业务层需要一些持久层中用不到的字段(DTO需要比实体类更多的字段),那么对于持久层来说,会有多余字段。
3、数据库对象的立即加载
当尝试获取某个记录的子记录时,根据懒加载原则,会自动查询数据库获取子记录。但若我们只是想要查看前端是否传了子记录进行非空判断,这种表现层与持久层混用域模型的方式,将使这样的判断不可行。
4、发生非预期sql操作
编者工作中发现,在使用hibernate持久层框架时,调用实体类的 setter 方法会引起框架自动调用update语句同步setter操作到数据库(更新某个记录的某个字段、对标注了OneToMany等关系字段的关联表进行级联更新或删除)—— 但这不是我们想要的。
@Entity
@Table(name = "app_user")
public class User implements Serializable
{
@Id
@SequenceGenerator(name = "id_app_user_seq", sequenceName = "id_app_user_seq")
@GeneratedValue(generator = "id_app_user_seq", strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
/**
* 密码
*/
@Column(name = "password")
private String password;
/**
* 拥有的角色列表
*/
@ManyToMany(targetEntity = Role.class, fetch = FetchType.LAZY)
@JoinTable(name = "app_user_role_relation", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles;
//省略 setter、getter...
}
//存储了一条数据库记录的实体类对象:entity
...
entity.setPassword(null);
entity.setRoles(null);
HttpServletUtils.updateUserInSession(session, entity);//持久化对象到session内存中
return entity;
//本方法结束后,会自动执行update user表置空本记录的password字段值,且执行delete app_user_role_relation删除本记录在映射关系表中对应记录。
//然后本方法的本意是避免用户敏感信息存储到session中,所以这些连带sql 是不可接受的
2、若在不同的层次使用不同的域模型
结构独立,使用更加灵活;易于扩展;对于需要对接不同客户端的服务,可以使用不同的表现层实现业务包装。
3、若混用域模型
可以根据业务的复杂程度,对不同模块使用不同的域模型