郑晔谈 Moco 框架的开发:写一个好的内部 DSL,写一个表达性好的程序

  • 张龙

2013 年 7 月 19 日

话题:Java测试语言 & 开发

Moco是一个简单搭建模拟服务器的程序库 / 工具,这个基于 Java 开发的开源项目已经在 Github 上获得了不少的关注。该项目的简介是这样描述自己的:

Moco 是一个简单搭建 stub 的框架,主要用于测试和集成。这个框架的开发灵感来自 Mock 框架,如MockitoPlayframework

为什么要开发这个框架?

集成,尤其是基于 HTTP 协议的集成——web service,REST 等,在我们的项目开发中被广泛应用。

以前,我们每次都要往 Jetty 或 Tomcat 等应用服务器上部署一个新的 WAR。大家都知道,开发部署一个 WAR 的过程是很枯燥的,即使在嵌入式服务器上也是如此。而且,每次我们做一点改动,整个 WAR 都要重新组装。

Moco 的出现,正是为了解决这些问题。开发团队只要根据自己的需要进行相应的配置,就会很方便得到一个模拟服务器。而且,由于 Moco 本身的灵活性,其用途已经不再局限于最初的集成测试,比如,Moco 可以用于移动开发,模拟尚未开发的服务;Moco 还可以用于前端开发,模拟一个完整的 Web 服务器,等等。

之前的《企业系统集成点测试策略》一文曾对 Moco 的基本用法和背后的一些理念进行了介绍。今天,我们邀请到了 Moco 框架的开发者郑晔,来跟我们分享一下 Moco 设计背后的一些思路。

InfoQ:郑晔你好,能否向大家做个简单的自我介绍、现在所从事的工作以及感兴趣的方向。

郑晔:大家好,我是郑晔,一个有十多年工作经验的程序员,现在在 ThoughtWorks 工作。这些年做过很多事情,包括开发和咨询,除此之外,做过演讲,也写过文章,翻译过书,也贡献过开源,愿意与人畅聊技术,也愿意分享自己的经验。个人一直热衷于探索各种程序设计语言在真实软件开发中所能发挥的威力,致力于探寻合理的软件开发方式。我的 blog 是梦想风暴,新浪微博是@dreamhead

除了日常开发之外,近期的一项工作是,整理现代 Java 开发的一些知识,因为 Java 从诞生至今已经快 20 年,但现在很多 Java 程序员的代码编写方式和开发理念还停留在至少 10 年前。这些年,我涉猎过很多不同的程序设计语言和不同的项目,把其中学到的一些理念应用回 Java 开发,可以很大程度上提升 Java 原本笨拙的方面。我准备整理一下自己在这方面的理解分享给其他人。一些内容已经写成《你应该更新的 Java 知识》系列,发布在自己 blog 上。其中涉及到很多函数式编程的思想,比如,使用函数组合进行构建,这样的思想已经很好地体现在 Moco 的内部 DSL 设计上。

InfoQ:对于企业系统的集成测试,如果采取自行开发模拟服务器的方式,想必需要投入不少人力与时间成本,Moco 在这方面的考量是什么,它是如何解决这个问题的?

郑晔:在企业级开发里,集成几乎是躲不开的。为了不耽误开发进度,开发团队通常都会开发一个模拟服务器,模拟要集成的服务。从十多年前我正式工作的第一个项目开始,我就在做这件事。当时刚开始工作,非常生猛,我直接从底层的 Socket 开始直接编写了一个 Web 服务器,支持我们所需的服务。后来,在各种不同的项目中,我经常遇到类似的情形,不断重复地构建着自己的模拟服务。构建服务的过程,非常繁琐,我也尝试过很多不同的框架做这件事,比如,Servlet、Ruby 的 Sinatra 等。即便有框架支持,后期调整服务的过程也还是非常麻烦,比如 Servlet 需要重新部署,这是让人无法忍受的。

Moco 正是为了解决这种问题而生的。目前,Moco 直接支持的使用方式有两种,一种是 Java API,另一种是独立服务器。Java API 可以在我们在单元测试框架里,通过简单的配置,运行起一个模拟服务,而独立服务器的方式,则可以通过一个配置文件,配置模拟服务器。无论是 Java API,还是独立服务器,相对于传统做法,Moco 有两个非常明显的优势,一是提供 DSL,可以非常直白地表现目的,二是启动速度非常快,无需漫长的等待。

InfoQ:相比于传统的 Jetty 容器结合 Mock 框架(如 EasyMock 等)方式来说,使用 Moco 有哪些优势?

郑晔:我不知道是否真的有人这么用过。Jetty 一般是用在集成测试里,而 Mock 框架则是用在单元测试里,两种测试有着根本的不同。

我知道有类似于 Moco 的框架采用了 Jetty 作为底层的支持,但 Moco 没有走这条路。Jetty 的目标是做一个 Web Server,而不是一个模拟服务器,所以,以模拟服务器的需求看,它会有些重。另外一点,未来 Moco 可能不只做 Http 服务,也可能会做 Socket 服务。现在已经有些人向我提出了这方面的需求,也许会在后面的版本里实现。

至于 Mock 框架,其主要是在对象级别进行模拟,而 Moco 是在模拟服务,二者模拟目标的级别是不同的。

InfoQ:Moco 的设计采用了内部 DSL 方式,请问其实现原理与好处有哪些?

郑晔:我一直对程序语言的表达性有着非常强的追求,表达性好的程序,理解起来也更容易,无论是现在的开发之时,还是后续的维护之日,都有极大的助益。我个人即便是选择使用程序库,也会倾向于选择表达性好的程序库。而 DSL 就是表达性在程序设计语言中最好的体现。我是 Martin Fowler《领域特定语言(Domain Specific Language)》这本书中文版的译者之一。翻译 DSL 这本书的过程,加深了我对 DSL 的理解。

Moco 的基本想法其实早就在我脑子里了,但我用了很长时间去找的一种把它写成内部 DSL 的方式。我从 Mockito 中受到启发,构想出描述服务端的方式,就是现在的请求 / 应答的基本结构,但怎么把它更好地运行起来一直困扰着我,直到有一天,我看到 PlayFramework 在测试方面的做法,豁然开朗,于是有了现在的 running 结构。

虽然有了思路,但写出一个好的内部 DSL 也不是一蹴而就的。一方面,设计一个可读的 API 并不是容易,选择哪些名词、动词、连接词着实费了我很多思量,往往一个 API 的设计要花很长时间来起名。另一方面,Java 不支持集合字面量,所以,很多东西为了表达性只好采用函数,用其返回的对象表示一个基本概念,但 Java 的类型系统又有很多限制,比如,同样在请求和应答里都可以有文件的概念,但如果想用同一个语法表达文件这个概念,就必须有个中间层,分别适配请求和应答。

InfoQ:Moco 还可以独立服务器的方式使用,这种方式与传统的 Tomcat 方式相比的优势有哪些、劣势有哪些、功能上有哪些差异性?

郑晔:无论是 Tomcat,还是 Jetty,它们首先是一个 Servlet 容器,是为了部署 Servlet 存在的,本身要支持 Servlet 的诸多特性,所以,对于模拟集成服务器这个需求而言,都是非常复杂的,Moco 只是针对模拟服务这个特定的需求,就要简单很多。这样的差异有一个非常直观的体现:启动速度。我演示 Moco 时,很多人都惊讶于 Moco 飞快的启动速度。

这里面有一个很重要原因是,Moco 最先实现的是 Java API,初衷是为了把它用在单元测试里。如果启动速度慢,就没人会用了。所以,在开发时,我没有选择一个已有的容器,而是选择了一个 Netty 这个网络应用开发框架作为基础。

当然,Moco 毕竟只是一个模拟服务器,它不像 Servlet 容器功能那么强大,可以做许多事情。说来有趣,在开发 Moco 的过程中,我曾冒出过这样的念头,让 Moco 支持函数,这样,用户就可以自定义一些简单的行为了。但后来,我打消了这个念头,一旦引入函数的概念,Moco 就开始像一个真正的服务器,而不再是一个纯粹的模拟服务器,和我的初衷不一致了。也许有一天,我会写一个基于 Moco 理念的新 Web 框架,但它绝不是 Moco。

其实,Moco 独立服务器是个无心之作。我最初只是想实现 Java API。后来看到一些类似的框架支持配置文件的形式,就顺手写了一个。没有想到,它会成为很多人使用 Moco 的一种重要方式,它也反过来影响到 Moco 核心 API 的设计,比如,现在 Moco 涉及的文件部分都是在运行时动态加载的,这就是为了满足独立服务器运行的需要,不必为了修改一个文件,反复重新启动。

InfoQ:在开发 Moco 时曾遇到过哪些棘手的问题?

郑晔:从技术上说,Moco 不是一个很复杂的东西,实现难度不是很大。Moco 最初的一个发布版,只有不到 3000 行代码。但这里面也有一些我的思考,我倾向于把事情做简单,因为把代码写多很容易,而写少却不容易。简单的东西维护和扩展都相对容易一些,才会走得更长远。无数的经验已经证明,复杂的东西只会为自身所累。我做咨询的时候,看到过很多动辄百万代码的项目,项目里的所有人都很挣扎,我们作为咨询师经常可以很轻松地把代码规模砍到原来的一半,因为里面重复代码太多。最近,我的一个同事为一个电信项目设计了一个新的模型,原来数百万行的代码一下子缩减到十万的规模,这是数量级的提升。很多软件最初的设计都是不错的,只是往往在最不起眼的写代码层面上搞砸了一切。我曾经在 InfoQ 上写过一个系列的《代码之丑》,就是我在各种项目中看到的代码没写好的地方。

Moco 开发中如果说有一段棘手的日子,那是大约在开发两个月之后,所有的基础功能都实现了,我一度认为 Moco 的开发到此结束了。但后来,我把 Moco 介绍给更多的人,有人给我更多反馈,有人开始把 Moco 应用到实际的项目中。新的需求也就随之而来了,才有了后来熊节写的那篇文章《企业系统集成点测试策略》。而且,随着应用越来越广泛,各种新的用法也就涌现出来了,有人在移动开发中使用 Moco 模拟服务端,可以在没有真正服务端的情况下,进行移动端的开发;有人在前端开发中使用 Moco 模拟服务端,以便快速建立原型。所以,作为一个开源项目,真正生存下来的动力是有用人用,而不只是闭门造车。

编辑注:在今年的Duke's Choice Awards上,Moco 框架被提名为最具创新力的 Java 项目之一。文中提到的《企业系统集成点测试策略》一文同时也在 InfoQ 英文站发布过,并在 Twitter 上得到了 Martin Fowler 的关注。

Java测试语言 & 开发