码农一年小结

还差几天就工作满一年了,记录一下一些不足以写成博客的经验吧。

关于数据库访问层

做后端开发必定是要访问数据库的,经历的几个项目分别使用了 JDBCTemplateQueryDSL ,一年的使用下来,让我对 JPA 越来越厌恶。

QueryDSL

QueryDSL 是基于 JPA 的,它为我们提供了一种类型安全的类似于 SQLdsl。从语法上来看,确实能带来一定的价值,但相对于负面作用,我认为是得不偿失。

  1. 需要预编译

    要使用 QueryDSL,需要先利用它的工具对 @Entity 注解的类进行编译,生成一个 Q 开头的类,用来实现便利的查询工作。虽然不是一个麻烦事,但确实在工作中造成了一些问题。比如在进行单元测试调试时,如果有对 @Entity 的修改,还得手动的执行一下编译任务。

  2. 生成的 SQL 有可能不符合预期

    这是很有可能发生的事情,特别是对于新上手 QueryDSL 的人来说,更是摸不清头脑。这样就需要在每次修改了 DAO 类之后运行一下测试或者启动一下项目才能知道生成的 SQL 到底长什么样子。不过一般来讲,当你熟悉他的语法之后,这样的事情并不会造成太大的困扰。但即使是这样,我们仍然要在每次修改了 DAO 类之后查看一下生成的 SQL 是否符合我们的预期。因为我们就遇到过一些神奇的事情,得到的 SQL 并不符合预期,想来想去只能是 QueryDSLbug

    自动生成 SQL 还会带来一个问题就是,开发者失去了对 SQL 的控制。我曾经也是“在代码里不要出现一句 SQL”的坚定支持者,但是当我经历了不得不对 SQL 进行定制时,才发现这种想法多么可笑。当然,QueryDSL 有办法通过 Expressions.stringTemplate() 方法来定制你想定制的那部分 SQL,但这和其他的 QueryDSL 组合起来时,总显得那么格(bu)格(gou)不(you)入(ya)。

  3. JPA 事务对测试的影响

    我们一般会在 Controller 的下一层,也就是 Service 这一层加上 @Transactional 注解,这样当我们的某个环节出错的时候,就能整个事务回滚,保证操作的原子性。对于这中间的这些 Service ,我们一般会采用没有 Spring 上下文的单元测试,这样可以让这部分的测试负担没有那么重。但是因为 JPA 的加入,情况变得复杂,就算我们的代码通过了这样的单元测试,也不能保证能够正确的执行,测试也就变得没有意义。一种解决办法是不再采用这种单元测试,而使用有 Spring 上下文的测试,这样能够将 JPA 的行为纳入测试中。

    但我个人来讲,可能是由于对 JPA 不足够熟悉,不太愿意继续使用 JPA,而更愿意使用 JDBCTemplate

JDBCTemplate

其实一开始使用 JDBCTemplate 的时候我是抗拒的,那时我还是 JPA 的支持者,总觉得在代码里面插入 SQL 很不优雅。但是在业务复杂的项目中使用过 JPA 后发现,JDBCTemplate 真香。相比与 JPAJDBCTemplate 有这些优势:

  1. 更轻量

    这一点应该毋庸置疑,毕竟少了一大堆复杂的东西。

  2. 开发者能够完全控制 SQL

    特别是当你需要特别的 SQL 的时候,会爱上这一点的。至于代码和数据库解耦这种事情,我的理解是我的数据库可以随意更换,但都是一种数据库,很难有从 MySQL 切换到 PostgreSQL 这种事发生。所以,代码里写 SQL 带来的耦合不一定会让你痛苦。

  3. 无需担心测试通过但不工作的情况

    没有了 JPA ,我们的 Service 由我们全权控制,也就不会出现因为 JPA 事务导致通过测试的代码不能正确执行的情况。

测试用的数据库

在我们的代码部署到 dev 环境之前,一定是会经历一个 CI 的过程的。在 CI 中,我们会运行我们写的所有测试,其中就包括针对 DAO 的测试,这些测试一定是要访问数据库的。那么在 pipeline 中执行测试时访问什么数据库就是一个问题。

过去,我们会考虑采用保存在内存中的 H2 数据库,它随我们的测试启动而启动,随测试结束而结束。看上去这简直是个完美的方案。然而,我们实践发现了一些问题,H2 不支持 MySQL 的全部语法,即使设置了 model=mysql。这使得我们不得不采用它支持的语法来绕过这些问题,甚至于需要迁移 JSON 格式的数据时会发现 migration 没法进行下去了,因为 H2 压根没有 JSON 的任何支持。最后,我们幸运的利用 flyway 支持的 Java Migration 绕过这个限制。

所以我们又重新思考有什么别的方法可以解决当前的问题。

其中一个方案是,在执行测试时,利用 Gradle 的插件启动一个 Docker 容器,测试完成后删掉这个容器。但是这样就要求我们的 pipeline 能够支持 Docker。并且,我们在调试 DAO 测试时也没有办法使用到这个容器中的数据库。不过这两个问题也有办法解决,这个方案也是值得尝试的。

另一个方向是同事前几天发现的 MariaDB4j ,单从这篇文章来看,对于使用 MySQL 的项目,确实是替代 H2 的不错方案。

HATEOAS

HATEOAS 是 Hypermedia As The Engine Of Application State 的缩写,处于 Richardson 博士提出的 REST Maturity Model 中的 Level 3,可以用来引导客户端探索服务端功能。

说实话,我对这个东西的理解不是很深刻,目前实践来看,更适合 Mobile 这样的单一入口的项目,而不适用于网页应用。这是因为整个应用需要一个入口,从这个入口进入才能获得更多的服务。或者,是我们的实践不好,应该支持任意入口进入都能获得完整的服务地图。

DDD

领域驱动设计绝对是能够帮助你降低项目无谓复杂度的方法论,当然,前提是你的项目足够复杂。至于如何判断呢?我的理解是当你修改一个业务逻辑却需要到多个地方修改同样的逻辑、或者干脆不知道在哪里能够找到你想找的代码时,就可以考虑 DDD 了。

之所以着么推荐 DDD ,是因为在体验了没有采用 DDD 的项目需求变得日益复杂之后,才发现 DDD 的优势。他能够帮助开发者对代码有更加清晰的了解,也能够将业务表达在代码中。这样既可以将相同的业务代码集中,也能够帮助开发者更容易的找到代码。


以上就是一年工作的一点经验总结,看起来没什么进步,还是要多写博客才行啊。