我意识到像Morphia和Hibernate这样的持久性框架依赖于域对象上的注释来实现它们的魔力。在某种程度上,在我看来,这是在域层中插入持久性问题,这是我们应该努力避免的。
这是否应该通过使用外部配置文件或可能与域模型中的单独DTO来避开?或者,持久性和域层之间的这种小漏洞通常被认为是可以接受的吗?
答案 0 :(得分:13)
在我使用Spring和Hibernate的现有系统的最新版本中,我已经开始采用类似的方法。在第一次实现Hibernate模型时,我努力通过数据访问对象将服务类中的应用程序逻辑与持久性逻辑分开。在去年构建新系统时,我允许大多数持久性对象充当域对象,因为这是权宜之计。
然而,我正在根据不断变化的业务需求重新设计同一系统,我再次倾向于将这些问题分开。我只是进入新设计的几天,但我发现自己更喜欢有一个表示内存中关注对象的对象,而另一个基于持久性的对象用于将其状态更改存储到数据库中。例如,我有一个Lead
用于持久性,一个并行ActiveLead
用于交易。
我还不相信这是最好的方法,但它在肠道水平上才有意义。我渴望拥有一组持久性 - 不可知 - nay,persistence- 无知 - 在数据库事务中保持内存驻留的对象集,而不考虑标准的CRUD简化。但据我所知,最终所有数据库操作都实现为CRUD。两个世界必须相互碰撞,诀窍在于让它们和谐地跳舞。
域对象上的Hibernate注释?在我看来,这是在易于实施和易于维护之间的良好折衷。
答案 1 :(得分:9)
域对象中的持久性注释是不是很糟糕吗?
是即可。随着NoSQL的兴起,你不能依赖单一的持久性策略。
例如,今天我将我的域对象(假设使用morphia)保存到MongoDB中。 如果明天我想将域对象持久化到Neo4j怎么办?
或者,人们可能希望将域对象持久保存到所有三种数据库,如关系(Postgres / MySQL),MongoDB(文档存储)和Neo4J(图形数据库),仅用于评估。
在所有这些情况下,最好有单独的持久性策略,而不仅仅是依赖域对象
最佳实践:将持久策略作为策略模式传递可能有所帮助。但是在设计类/对象时必须要小心。
答案 2 :(得分:8)
我最近在一个相当复杂的系统上工作,有一个单独的持久层,这是一个巨大的痛苦,可维护性很差。你基本上是在看YAGNI和单一责任原则之间的冲突。在我看来,YAGNI是更重要的一个(唉,也是更常被忽略的一个)。
我会说在绝大多数情况下,如果你使用ORM,直接持久保存域对象要好得多,除非你有具体要求迫使持久性实体结构化不同的(如果它们具有完全相同的结构,除了象牙塔参数之外没有理由将它们分开)。
确定:总是在单独的服务/ DAO层中执行实际的持久性内容(调用ORM函数)!这样,如果您发现需要它,很容易在以后引入持久层。
答案 3 :(得分:5)
我相信如果我已经决定使用我将要使用的持久性框架,我会在我的域上使用注释,但是,如果你遵循Hexagonal架构和TDD,XML会更方便。如果您使用特定框架预先注释您的域,那么您将与持久性集成相结合,无法测试核心功能,目的是使技术/框架无关。
答案 4 :(得分:4)
在我看来,没有必要复制域对象,以便能够从持久层中分离它们。它可以处理重复的代码,通过使用与DTO相同的对象,它是完美可行的。如果需要,你可以随时使用单独的课程,但我不会把它作为一个经验法则,它会花费你的时间,我们都知道时间是有价值的。
答案 5 :(得分:3)
简短回答:我喜欢持久,丰富的域名对象。
答案很长:
近10年来,我使用Spring和Hibernate在一个相当大的系统~500k LOC上工作。一开始,我们开始使用“事务脚本”(参见Fowler)方法,部分原因是我们并不完全信任Hibernate。然而,在很短的时间内,我们开始相信Hibernate,并且由于我早期在相当纯粹的OO中的训练,我成为了瞬态持久性结合域驱动设计方法的忠实信徒。我们基本上认为我们的系统支持ODBMS(有很多小泄漏: - ))。
我将我们的架构称为“域内核”,因为还没有编写DDD这本书。这是在Hibernate的早期阶段,因此域模型没有被注释污染。持久性的单独关注在XML映射中保持独立。
同样,随着时间的推移,我们会更好地将行为推向域层。我们有一个非常传统的控制器 - > service - > dao - >域分层方案,它是使用编译时依赖项强制执行的。我观察到的是,这个模型对我们的系统非常有效,它代表了401(k)计划管理相当复杂的领域的每个方面,包括计划设置,交易,会计,合规测试,销售,品牌等。具有(相对)透明的“魔法”持久性的丰富域模型是我们能够根据域模型中的现有功能构建新功能的关键。
我们的服务层仅协调技术服务(例如电子邮件,文件I / O,排队等)之间的交互,并在必要时帮助跨域包。服务层还定义了事务边界(通过Spring)。服务仅接收或发出DTO或原语。很多人讨厌这个,作为DRY中的一个突破,但我们发现它在定义服务接口和使用它们的代码时保持诚实。它还使以后很容易远程处理。
这种方法允许我们用一个非常小的团队(我们是一个Scrum团队)构建高质量的软件。
所以,请考虑我是持久域对象的信徒。不知道我的故事是否有帮助,但我想分享。
答案 6 :(得分:1)
在DDD社区中发现的东西
克里斯·理查森(Chris Richardson)发表 *如果您想将JPA排除在域模型之外,请使用XML而不是注释(自从IMHO以来,我就从来不喜欢ORM注释) 混合了疑虑)
我个人非常喜欢使用批注,XML对我而言总是容易出错,字段名中的一个小更改,您也需要手动更改XML。如果要重构域的单个类,最终可能会更改多个文件,而不是自动对其进行处理。 但是最近,我一直在重新考虑这一点,因为我希望能够在项目中使用多个持久选项。我不需要任何与持久性相关的内容,因此XML是一种选择。 仍然有几次我到达没有直接映射的地步,或者我仍然想使用注释,因为它们很容易更改并且可以直接在代码中看到。 我最近一直在做的事情是创建我的业务域类,并将其作为抽象类,并为持久性扩展而使用另一个用户类。像这样:
public abstract class Persona {
private Set<State>states;
public boolean inState(State state){
return states.contains(state);
}
}
如果由于某种原因有一个数据库,其中状态已经被定义为单个列,并且不可能进行直接映射,那么我可以扩展业务类并将其用作持久性实体。
@Entity
public class PersonaSql extends Persona {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String statesDefinition;
@PrePersist
void prePersist(){
this.statesDefinition = mapStatesSetToString();
}
@PostPersist
void postPersists(){
this.states = mapStatesStringToSet();
}
}
当然,这是一个简单的例子。还有其他方法可以解决此问题,我的观点是:通过使用继承,您可以利用处理注释的巨大优势,并使您的业务模型不了解特定的持久性代码。
不使用继承的另一种选择是将持久性实体转换为业务模型,反之亦然,但是我不建议您采用这种方法(即使使用automapper之类的方法),除非您的域很简单并且您确定它将保持简单。例如,如果您要创建微服务,则您的域应该足够简单,并且应该很简单。
答案 7 :(得分:0)
我更喜欢带有注释的富域域对象。甚至埃文斯也在他的sample app中使用了这种方法。他使用XMl而不是Annotations但他仍然坚持使用相同的对象。
分离域和持久性可能更加干净,但是不能仅仅为了能够在将来选择不同的数据库技术。这是复杂地狱的方式,YAGNI先生会咬你。