讨论一种低耦合的数据持久化方案

分类: 架构设计

保留所有版权,请引用而不是转载本文(原文地址 https://yeecode.top/blog/81/ )。

在软件开发过程中,进行持久化操作是十分常见的,甚至很多后端程序员谑称自己为CRUD工程师。

传统层式方案

通常,我们进行数据持久化时,会使用Service、Dao、XML(例如MyBati中配置的XML)这三层结构。然后,对象在这三层结构中流动,如下图所示。

向下流动便完成了对象写入数据库的操作,向上流动便完成了对象从数据库读出的操作。

这样的持久化方案十分简单且通用,因此得到了非常广泛的应用。但是也存在以下几个问题:

首先是,耦合严重。一个对象和它的Service、Dao、XML都是存在耦合的,其耦合点很多。

其次是使用复杂,例如要完成一个对象的持久化,则需要先找到它的Service然后调用。在进行包含多种对象的批量操作时,找到某个对象的Service的过程将变得十分复杂,往往需要按照一定的命名规则进行反射。

例如,对不同类型对象的集合进行持久化,则如下所示:

for(Object obj : objectSet) {
	// 找到该对象对应的Service
	ObjService objService = findServie(obj);
	// 调用其Service提供的持久化功能
	objService.insert(obj);
}

findServie方法将会是一个较为复杂的方法。

第三,其不太符合面向对象原则。因为,持久化这件事情,应该是某个对象自身的行为,没有必要依赖外部的其它类完成。

对象行为式方案

我们可以尝试将对象的持久化作为对象自身固有的行为。

例如,在对象中设置一个saveToDb()方法。只要调用某个对象的saveToDb()方法,则将该对象持久化。按照这种方式,如果对不同类型对象的集合进行持久化,则如下所示:

for(Object obj : objectSet) {
	// 持久化该对象
	obj.saveToDb();
}

显然,操作变得更简单。

更重要的是,saveToDb()方法是对象自己的行为,与toString()等方法一样,即如下图所示。

这更符合面向对象的思想。

当存在大量对象时,这一方案带来的便利性更加凸显。例如存在下图所示的一棵树。

则每个节点的持久化方法可以这样写:

save() {
	this.saveToDb();
	for(Node node: this.children) {
		node.save();
	}
}

然后调用根节点的save()方法便可以依次触发各个对象节点持久化,逐级触发,一气呵成。

而不同对象节点的saveToDb()可以自由掌控,按照不同的规划放入不同的表中。

对象行为式方案中的查询

为了实现单个对象的查询,我们可以设置一个queryfromDb()方法,表示从数据库读取对象的数据。但是,读取数据之前,则必须要事先写定一些索引信息。例如下面的方法表示以id=5和name="易哥"为索引,从数据库中查询出指定的User对象。

User user = new User();
user.setId(5);
user.setName("易哥");
// 以上面的既定信息为索引,去数据库查询一个满足条件的User对象
user.queryfromDb();

如果查询的类型比较多,可以演化出queryFromDbById()、queryFromDbByName()、queryFromDbByIdAndName()等诸多版本。但是总体而言,这比传统层式方案要更清晰,也没有增加复杂度。

真正让对象行为式方案稍显疑惑的是批量查询,不过也有具体的解决方案。

如果存在多个age=18的User对象,则下面的查询就无法进行。

User user = new User();
user.setId(18);
user.queryfromDb();

因为,queryfromDb()这一行为可能会得到多个对象,因此,它不是在属于一个特定对象的行为,而是属于一类对象的行为。

一类对象,就是类啊!

因此,当查询出的结果是多个User时,这个queryfromDb()方法应该属于这个类,而不是具体对象。因此,它是一个类方法。

所以:

  • 查询单一对象是对象方法
  • 查询批量对象是类方法

但是考虑到单一对象查询也可能出现查询不到的情况,即查询结果为null。因此,将所有查询操作都作为类方法是合理的。

同理,删除方法也作为类方法。因为,删除也可能是批量的。

对象行为式方案的落地

在具体实施时,我们建议只保留saveToDb()、queryfromDb()、delFromDb()这三类方法。

其中,具体实施如下:

  • 对于查询方法,使用类方法queryfromDb(),可以演化出众多版本,例如queryFromDbByAge()、queryFromDbByName()、queryFromDbById()等。
  • 对于删除方法,使用类方法delFromDb(),可以演化出众多版本,例如delFromDbByAge()、delFromDbByName()、delFromDbById()等。
  • 对于新增操作,创建完对象后,调用对象方法saveToDb()。
  • 对于修改方法,查询完对象,修改结束后,调用对象方法saveToDb()。

在上述方案中,存在以下的问题,不严重但也值得总结:

  • 更新方案效率稍低,因为其将更新拆分为了一个查询加一个修改

但总体而言,该方案要比传统层式方案更好,尤其是在更新操作较少的系统中。并且在面对大量对象持久化需求时,它能极大地提升项目整体的清晰度。

这种对象行为式方式,我们已经在开发中进行了运用,并收到了良好的效果。

可以访问个人知乎阅读更多文章:易哥(https://www.zhihu.com/people/yeecode),欢迎关注。

作者书籍推荐

作者书籍推荐 作者书籍推荐