软件开发架构师

Expedia是如何摆脱Java Bean转换器的

架构 151 2019-04-17 22:36

本文关键点

  • 让开发人员用一些上班时间用于创新,可能会他们展现出他们的潜力,在日常工作中创造出有用的解决方案
  • 开源,一个解决共性问题的产品或解决方案可能会帮到其他的人
  • 充满激情是打造优秀软件的关键要素
  • BULL 能有效地加速开发阶段,同时减少引入错误的可能性
  • 在实际应用中,BULL 不会对性能造成任何负面影响

在分层体系结构中,会通过将变化封装为特定的数据对象并传播到其他层,以创建出抽象级别,这种对象映射过程可能会变得强制且繁琐。
传统上,这种数据对象映射由手动编码转换器来处理,这些转换器在实体 (或 Java bean) 之间复制数据。
从不同的数据对象映射到不同的数据对象,需要大量的时间和成千上万行代码,当然,具体取决于你需要转换的数量。
这需要巨大的工作量,特别是如果还考虑到必要的测试用例的数量的话。这种类型的转换代码编写起来相当枯燥,而且极易出错,所以为什么不自动执行呢?

问题

幸运的是,有很多基于 Web 的映射框架可以为我们做这件事,所以我们不必担心!
但是,如果您的 Java bean 遵循常见的安全最佳实践是不可变的,会是什么情况呢? 其结果是,没有哪款映射框架能够以一种简单的方式转换你的对象。

解决方案

一个能够自动转换各种 Java bean(包括不可变的、可变的和混合的) 的类库可以生我们免去大量的工作,BULL就是这样做的。

Expedia是如何摆脱Java Bean转换器的-1

好处

为了更好地展示这个库的好处,让我们来看一个例子:假设您正在处理一个多层应用程序,其结构如下:

Expedia是如何摆脱Java Bean转换器的-2

FooController 包含一个 Rest API,它在输入中获取一个请求,并在执行了一组操作之后返回一个响应。

为获得响应,FooController 需要在这三层移动请求 / 响应数据 (由类似于这样的领域对象进行映射),所以我们需要为它们中的每一个都实现一个转换器 (及其测试),它从一个对象复制数据到下一层的另一个对象,并按照需要进行修改。
综上所述,对于每一层,我们将需要划分如下 20 个类:

  • 1 个服务类
  • 1 个服务类的测试
  • 6 个领域对象
  • 6 个转换器
  • 6 个转换器的测试

3 层相乘共计 60 个类。

现在让我们用 BULL 来分析相同的场景:

Expedia是如何摆脱Java Bean转换器的-3

现在的情况看起来很不一样了:不再需要转换类了,所有 3 层总共需要的类减少到了 24 个,总体来说代码减少了 60%。这意味着:

  • 更短的开发时间 (从而直接降低成本)
  • 减少引入错误的可能性
  • 更容易维护的代码

BULL 能做什么

BULL 旨在使它的使用尽可能简单,实际上,转换一个对象只需要一行代码:

复制代码
ToBean toBean = new BeanUtils().transform(fromBean, ToBean. class);

从今天看来,可能以下功能会有用:

  • 支持不可变 bean 的副本。
  • 支持可变 bean 的副本。
  • 支持混合 bean 的副本 (有些字段是私有的,有些不是)。
  • 支持没有 getter 和 setter 的 Java bean 副本。
  • 支持通过注释进行验证
  • 支持带有 Java 基本类型的副本
  • 支持带有 Java 集合类型的副本
  • 支持带有嵌套的 map 字段的副本
  • 支持包含基本类型而不是对象类型的数组的副本
  • 支持带有字段名称映射的副本
  • 支持带有递归副本的副本
  • 支持 lambda 函数字段转换

在此,提供了一份完整的特性列表,包括示例在内一直在更新。

现实世界中的转换

我们知道,在现实世界中,其实很少只需要在两个几乎相同的 Java bean 之间复制信息,通常会发生以下情况:

  • 目标对象的结构与源对象完全不同
  • 在复制特定字段值之前,我们需要对它执行一些操作
  • 必须验证目标对象的字段
  • 目标对象比源对象多一个额外的字段,它的内容需要取自不同的源

怎么解决这些情况呢?BULL 让你有条件在一个特定的字段上执行任何类型的操作!

利用lambda 表达式,开发人员可以定义自己的方法,在复制一个值前首先应用这个方法。

让我们来看一个例子,它可以更好地解释这一点,假设源类如下:

复制代码
public class FromFoo {
private final String id;
private final String val;
private final List<FromSubFoo> nestedObjectList;
// 所有参数的构造函数
// getters 函数
}
以及目标类如下:
public class MixedToFoo {
public String id;
@NotNull
private final Double val;
// 构造函数
// getters 和 setters 函数
}

假设 val 字段需要乘以转换器中的一个随机值,我们有两个问题:

  1. val 字段的类型与源对象不同,一个是 String 类型,一个是 Double 类型
  2. 我们需要指导该类库如何应用数学运算

其实,这很简单,你只需要定义你自己的 Lambda 表达式:

复制代码
FieldTransformer<String, Double> valTransformer =
new FieldTransformer<>( "val",
n -> Double.value Of(n) * Math.random ());

该表达式将应用于目标对象中名为“val”的字段。
最后一步是将函数传递给 BULL 实例:

复制代码
beanUtils.get Transformer()
. with FieldTransformer(valTransformer)
.transform(fromFoo, MixedToFoo. class);

关于“字段验证”这一方面,它甚至更简单,因为您只需要从现有的javax.validation.constraints中根据需要选择一个 (或定义一个自定义的约束) 注释您的字段,仅此而已。

我们是怎么做到的?

当我们过去实现大量的 Java Bean 转换器时,所需的时间常常比在核心特性上投入的还要多,于是我们决定开始着手加速我们的开发过程,处理可能对所有人都有益的同样的问题。

得益于 Hotels.com 的公司文化,我们每周有半天的时间用于创新项目。利用这个机会来实现这件事,以及其他产品,现在正在显示它们的好处。在这个具体的事例中,我们显著地缩短了我们正在做的几个特性的开发时间。

为什么开源

  1. 开源软件也具有长期的生存能力。它是由一个全球社区创建和支持的,这个全球社区由许多组织和个体开发人员组成,其中许多开发人员也以协作和志愿服务等开源价值观作为生活的信念。
  2. 开源软件是由开发人员社区支持的。这些开发工作室不断地审查他们所支持的开源软件代码,就像有全世界成千上万的独立开发人员致力于这个项目一样。于是,就有了一个庞大的同行审查过程,以确保安全和责任。
  3. 开源软件有很强的价值观——而且通常情况下,开源软件工作室和开发人员持有相似的价值观——他们提倡更多的社区参与、协作和志愿服务。他们相信,要共同努力,打造免费、高质量的产品,让营利性和非营利性组织都能获得这些产品。

这种信念强调了最好的开源软件工作室和开发人员的使命。它促使他们构建新的特性,并将这些特性贡献给社区。直接的结果是,流行的开源软件项目往往都站在技术的最前沿。

有用的链接

结论和本文关键点

  1. 让开发人员用一些上班时间用于创新,可能会他们展现出他们的潜力,在日常工作中创造出有用的解决方案
  2. 开源,一个解决共性问题的产品或解决方案可能会帮到其他的人
  3. 充满激情是打造优秀软件的关键要素
  4. BULL 能有效地加速开发阶段,同时减少引入错误的可能性
  5. 在实际应用中,BULL 不会对性能造成任何负面影响

关于作者

Fabio Borriello拥有计算机工程硕士学位和 Web 技术硕士学位。他是一位才华横溢、充满激情的 J2EE 软件工程师,在面向对象的分析和设计方面具有成熟的专业知识,具有负责软件开发生命周期的所有方面(从分析和设计到实现和维护)的优异记录。每次他需要面对一些新的东西的时候,都将其视为是一个挑战和学习新东西的好机会。

查看英文原文How Expedia Is Getting Rid of Java Bean Transformers

文章评论