采用Apache Camel的开源系统集成方案与Fuse IDE的使用
原文链接 https://dzone.com/articles/open-source-integration-apache
通过系统集成工具(项目),可以使不同平台上的多个应用以多种传输方式进行“交谈“。但可以想像,企业应用的扩张会使应用间的“交谈”急剧复杂化。这些复杂性主要由两个因素导致:
- 难以协调程序与传输的细节,以及
- 对于系统集成问题一直没有好的解决方案
你自己可以很轻松地让应用通过API或其他方式进行交互。我肯定每个人都知道如何将JMS消息发送到指定的消息代理;虽然这需要对JMS规范有一定深入的了解,但很多开发者却没有。但最重要的是如果要将该JMS消息发送到其他应用呢?你不仅需要小心翼翼地将JMS消息转发到另一个应用,还要了解下这个应用的相关概念。如果好几个应用要加入进来呢?相信你会很头疼吧。
如果不考虑与协议和API的交互细节,我们可以从高级的角度来设计应用间的交互。幸运的是,已经有很多规范的解决方案来应对企业集成问题。Gregor Hohpe与Bobby Woolfe的著作《企业系统集成模式:设计、构建与部署消息解决方案》将企业架构的设计经验锤炼成65个企业系统集成模式(EIP)。虽然很不错,但我们还是要自己手写这些模式中的所有代码;它们只是一些建议,但却不是拿来即用的解决方案。
Apache Camel是为解决这两个问题而生的。在本文中我会先对Camel做一个简单介绍。然后带大家使用Camel解决一个系统集成中的问题。最后展示一个新颖的图形化工具来简化Camel的使用。
Camel是什么?
Apache Camel是一个关注于简化系统集成的开源java框架。它提供了:
-
广泛使用的EIP的具体实现
-
与各种传输协议和API的连接性
-
连接EIP与传输协议的方便易用的DSL
图1(选自《Camel in Action》)展示了以上三个部分在Camel中对应的概念。为了便于理解Camel的组织结构,我会为大家介绍Component、Endpoint、Processor与DSL。除此以外还有很多其他概念,但不在本文讨论范围之内。
图1: Camel架构概览
Component是Camel中的扩展点,它为其他系统提供了可连接性。Camel的核心(core)项目非常小,在降低了依赖度的同时提升了可嵌入性,因此它只包含了13个必要的Component。但在core以外却有着超过80个的Component。为了将这些系统暴露出来,Component提供了一个Endpoint接口。通过使用URI以一种统一的方式在Endpoint间发送或接收数据。例如,从JMS队列aQueue中接收数据并发送到文件夹"/tmp"中,你可以使用像 "jms:aQueue"和"file:/tmp"的URI。
Processor用于Endpoint间消息的处理与传递。在Camel中,EIP被定义为Processor或Processor集。在编写本文时,Camel提供了超过40个EIP书中介绍的EIP模式和很多实用的Processor。
为了将Processor与Endpoint连接起来,Camel定义了多种常见编程语言的DSL。Camel也支持使用XML制定路由规则。如下是一些使用不同语言编写的具有同等功能的DSL。
- Java DSL
from ("file:/tmp").to("jms:aQueue");
- Spring DSL
<route>
<from uri="file:/tmp"/>
<to uri="jms:aQueue"/>
</route>
- Scala DSL
from "file:/tmp" -> "jms:aQueue"
在上面的示例中我们定义了一个路由规则,它会将"/tmp"文件夹中的文件加载到内存,然后用文件中的内容创建一个JMS消息并将消息发送到名为aQueue的JMS消息队列中。
这些就是支撑Camel的底层概念。因为Camel中添加了许多其他有趣的特性,我推荐大家读下我与Claus Ibsen撰写的《Camel in Action》,以便对Camel的功能有一个全面的认识。下面是一些Camel中的其他特性:
-
灵活的数据格式化与类型转换器:简化CSV, EDI, Flatpack, HL7, JAXB, JSON, XmlBeans, XStream, Zip等消息格式间的转换
-
灵活的语言:创建DSL中要用到的表达式或断言。这些语言包括EL, JXPath, Mvel, OGNL, BeanShell, JavaScript, Groovy, Python, PHP, Ruby, SQL, XPath, XQuery等等。
-
Camel中bean与POJO整合的支持。
-
利用消息方式测试分布式与异步系统的支持。
-
。。。。
骑士汽配(Rider Auto Parts)
接下来的这个例子是贯穿《Camel in Action》全书的虚拟摩托车配件业务。这个名为Rider Auto Parts(骑士汽车配件)的公司要为摩托车制造商提供配件。在过去的几年中,他们曾几次更改订单的接收方式。最开始,订单通过上传CSV文件到FTP服务器来提交。后来消息格式又变为XML。现在他们又弄了一个网站,通过HTTP将XML消息发送到网站上来提交订单。
骑士汽配要求新客户通过网站来提交订单,但由于与老主顾签订的服务等级协议(SLAs),他们必须兼容原来的消息格式与接口。在进行处理之前,所有的消息要转换成POJO格式。图2展示了订单处理系统的抽象表示。
图2: 骑士汽配订单处理系统
我们需要为上图中的"Ride order frontend"找到最佳的实现方案。我们先来看看《企业集成模式》中的表示方法。
采用EIP的解决方案
骑士汽配面临着一个很常见的问题;经过几年的运营,由于业务需要,软件需要兼容已经过时的传输协议与数据格式。采用EIP模式我们可以设想出如下的解决方案。
图3:采用EIP表示法的解决方案
在这里我们用到了几种模式:
-
两个消息Endpoint;分别用于FTP与HTTP的连接
-
将这两个endpoint中得到的消息注入incomingOrders消息管道。
-
用于处理incomingOrders消息管道中的数据并将其路由到两个消息解释器(Message Translator)的Content-Based Router。正如它的名字那样,路由的目的地取决于消息的内容。这里我们需要根据文件的类型(CSV/XML)来进行路由。
-
两个消息解释器都会将消息内容转换成POJO并传入orders消息管道。
使用一个Content-Based Router与多个消息解释器的部分称为标准化调节器(Normalizer)。组合模式图可以表述这个标准化调节器,但为了简单起见,这里只展示了它的子模式。
使用Camel的实现
正如前面所提到的,Camel默认只提供有限的核心组件。其他的组件作为独立的模块存在。在需要多种连接性的应用中,需要导入相应的Camel模块。列表1展示了Camel实现的骑士汽配所需的maven依赖。当然,你不一定非得用maven管理依赖——它只是添加依赖最简便的方式。这个列表中的依赖包括Camel core, ActiveMQ, JAXB序列化, CSV序列化与HTTP。为了简化问题,我决定使用File endpoint而非FTP,但如果使用FTP endpoint的话我们也要为项目添加camel-ftp模块依赖。
列表1:Camel实现的maven依赖
<dependencies>
<!-- Core Camel -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel-version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring</artifactId>
<version>${camel-version}</version>
</dependency>
<!-- Embedded ActiveMQ broker -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>${activemq-version}</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>${xbean-spring-version}</version>
</dependency>
<!-- ActiveMQ connectivity for Camel -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-camel</artifactId>
<version>${activemq-version}</version>
</dependency>
<!-- Add support for JAXB marshaling -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jaxb</artifactId>
<version>${camel-version}</version>
</dependency>
<!-- Add support for CSV marshaling -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-bindy</artifactId>
<version>${camel-version}</version>
</dependency>
<!-- Add support for HTTP -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jetty</artifactId>
<version>${camel-version}</version>
</dependency>
</dependencies>
虽然可以将Camel封装成一个独立的Java程序,但将它嵌入到容器中可能会更有用。因此,我们将使用Spring来加载Camel。实际上,整个解决方案(除了POJO与Maven脚本)可以很简洁的置于一个Spring XML文件中。因为我们采用了Spring XML来配置路由规则。Camel中的每个DSL都有不同的优缺点,但在本示例中我主要为大家展示一些与基于XML的路由规则配套的工具。Spring XML文件内容参见列表2
列表2: 配置了一个内嵌的ActiveMQ消息代理并用三个路由(route)初始化Camel上下文(Context)的完整Spring XML
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:amq="http://activemq.apache.org/schema/core"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://activemq.apache.org/camel/schema/spring
http://activemq.apache.org/camel/schema/spring/camel-spring.xsd
http://activemq.apache.org/schema/core
http://activemq.apache.org/schema/core/activemq-core.xsd">
<amq:broker brokerName="localhost" persistent="false" useJmx="false"/>
<bean id="jms" class="org.apache.activemq.camel.component.ActiveMQComponent">
<property name="brokerURL" value="vm://localhost" />
</bean>
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route id="FileToJMS">
<from uri="file:target/placeorder" />
<to uri="jms:incomingOrders" />
</route>
<route id="HTTPtoJMS">
<from uri="jetty:http://0.0.0.0:8888/placeorder" />
<inOnly uri="jms:incomingOrders" />
<transform>
<constant>OK</constant>
</transform>
</route>
<route id="NormalizeMessageData">
<from uri="jms:incomingOrders" />
<convertBodyTo type="java.lang.String" />
<choice>
<when>
<simple>${body} contains '?xml'</simple>
<unmarshal>
<jaxb contextPath="org.fusesource.camel" />
</unmarshal>
<to uri="jms:orders" />
</when>
<otherwise>
<unmarshal>
<bindy packages="org.fusesource.camel" type="Csv" />
</unmarshal>
<to uri="jms:orders" />
</otherwise>
</choice>
</route>
</camelContext>
</beans>
在这个文件中我们首先启动了一个内嵌的ActiveMQ消息代理,然后将之与Camel连接。接着在camelContext元素中定义了路由规则。根据图3所示,我们需要从FTP(本文中使用File替代)与HTTP endpoint中接收订单,数据格式如列表3中所示。在Spring XML配置中我们使用了两个from元素定义入口endpoint。“FileToJMS"与"HTTPtoJMS"路由以from元素开始。两个from元素都连接到一个URI为“jms:incomingOrders”的producer(to与inOnly元素),这会将消息发送到一个名为incomingOrders的ActiveMQ队列中。
列表3:传入消息格式;左边为XML右边为CSV
XML | CSV |
---|---|
<?xml version="1.0" encoding="UTF-8"?><br/><order name="motor" amount="1"/> | "name", "amount"<br/>"brake pad", "2" |
对于HTTP endpoint有几点需要注意。首先HTTP客户端需要得到响应,所以我们不得不进行处理。在Camel中我们可以完全控制客户端从HTTP endpoint处得到的响应。所有响应都由route定义中的最后一个方法决定。在这个例子中我们使用trsform将响应设为字符串常量“OK"。另外因为我们手动处理了请求结果,所以不希望从JMS incomingOrders队列中得到响应。使用inOnly元素我们可以以一种”发送就忘记“(向目的地发送消息后并不验证消息的接收)的方式向这个队列发送消息。
列表2中的"NormalizeMessageData"定义了一个Normalizer(标准化调节器), 它有一个Content-Based Router和两个Message Translator。首先我们指定了要从incomingOrders队列中取得消息。由choice,when,与otherwise元素实现了基于消息内容的路由。在示例中,我们希望将CSV与XML消息发送给不同的Message Translator。我们使用了一个Simple表示式来检查消息的数据格式,这个表示式可以检查消息体是否以<?xml标签开始。Simple是一个内置于Camel中的表达式语言。当然,这里主要是为了演示,在真实的案例中你可能需要更加全面的内容类型检查。
我们使用Camel提供的数据格式化将XML与CSV数据分解到一个Order对象中。列表2展示了如何使用unmarshal元素定义数据格式化。在列表5中,Order类使用了JAXB和Camel Bindy注解来表示对象与XML和CSV之间的映射。
列表5:Order领域类采用JAXB/Bindy注解实现与XML/CSV之间的映射
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@CsvRecord(separator = ",", skipFirstLine = true)
public class Order implements Serializable {
@XmlAttribute
@DataField(pos = 1)
private String name;
@XmlAttribute
@DataField(pos = 2)
private int amount;
public Order() {
}
public Order(String name, int amount) {
this.name = name;
this.amount = amount;
}
}
到这里,成功标准化后的消息会被发送到orders队列,然后由骑士汽配的其他程序做进一步处理。
使用Fuse IDE for Camel实现
正如我们看到的,Apache Camel极大地简化了系统集成。支持多种语言的DSL设计简洁,功能强大且易于阅读——所有这都是以提升开发者的效率为目的。本着提高生产率的目的,FuseSource专门为Camel开发了一款名为Fuse IDE for Camel的集成开发环境。
Fuse IDE是一款基于Eclipse的工具,通过在画布上拖拽并连接EIP图标来生成路由规则。IDE会在后台生成Spring XMl格式的Camel路由规则,所以你不再需要深究Camel DSL的细节。其实你只需要创建一个像图3那样的工程图,然后用Fuse IDE生成Camel程序就可以了。
图4展示了Fuse IDE designer(设计器)加载了列表2配置后的界面。
图4:加载了列表2中Spring XML后的Fuse IDE设计器
从设计器视图(disigner view)我们可以以图形化的方式预览在Spring XML中创建的路由规则。同时我们也可以改变EIP图标的连接方式、endpoint URI的属性或路由中的其他事物,等等。另外,Fuse IDE的1.0版本会支持Apache Camel中的全部EIP与组件——所以不需要为了Camel的某些特性而苦等2.0版本。
本文以手写Spring XML配置开始。虽然这样做不会对你产生误导——但却也不是很有必要。实际上,因为Fuse IDE支持Camel Spring XML与设计器视图间的相互转换,你可以选择自己喜欢的方式来设计路由规则。图5展示了Spring XML文件的源码视图(source view)。在源码视图中的操作都会对应到设计器视图(designer view)上。
图5在原文中没有
在设计好路由之后,你可以使用Fuse IDE提供的另一个功能:根据Camel Spring XMl文件生成JUnit测试用例。在测试通过之后,你可以在本地Spring容器中进行调试,或直接部署到容器中——这些都是Fuse IDE提供的。Fuse IDE后继会提供更多功能,所以Camel的开发会越来越简单。
总结
本文中展示了一个项目集成开发者可能遇到的两个常见问题:应用与传输的细节处理、为系统集成问题找到好的解决方案。Apache Camel很好地解决了这两个问题。正如示例所体现的,使用Camel解决系统集成问题是很直观的,并以简洁的路由定义作为产出。通过它我们看到使用Fuse IDE for Camel进行Camel开发是多么简单。