Zato—Python 中的敏捷 ESB、SOA、REST 和云集成

Zato 是一个基于 Python 的平台,用于集成应用程序并将后端服务暴露给前端客户端。它是一个 ESB(企业服务总线)和一个专注于数据集成的应用服务器。该平台在设计系统时不对架构风格施加任何限制,可用于 SOA(面向服务的架构)、REST(表述性状态转移)以及构建在内部或云端运行的系统。

在其当前版本 1.1(在撰写本文时),Zato 支持 HTTP、JSON、SOAP、SQL、AMQP、JMS WebSphere MQ、ZeroMQ、Redis NoSQL 和 FTP。它包括一个基于浏览器的 GUI、CLI、API、安全、统计、作业调度器、基于 HAProxy 的负载均衡器和热部署。每个部分都从多个受众的角度进行了广泛的文档记录:架构师、管理员和程序员。

Zato 服务器构建在 gevent 和 gunicorn 框架之上,这些框架负责使用异步通知库(如 libevent 或 libev)处理传入流量,但所有这些都对程序员隐藏,以便他们可以专注于自己的工作。

服务器始终是集群的一部分,并运行已部署服务的相同副本。单个集群可以包含的服务器数量没有限制。

每个集群将其配置保存在 Redis 和 SQL 数据库中。前者用于统计或频繁更新且主要为只读的数据。后者是服务器之间共享的更静态配置的保存位置。

用户可以通过 Zato 的 基于 Web 的 GUI命令行API 访问 Zato。

Zato 提倡松耦合、组件的可重用性和热部署。其高级目标是使访问或公开任何类型的信息变得非常简单。常见的集成技术和需求最多只需点击几下即可完成,从而无需在每次集成工作中不断地以略微不同的方式重新实现相同的步骤。

Zato 中的一切都是为了最大限度地减少组件之间的相互干扰,您创建的服务器端对象可以轻松更新、动态重新配置或在其他上下文中重用,而不会影响任何其他对象。

本文将指导您完成将复杂的 XML 数据通过 JSON、一种更简单的 XML 形式和 SOAP 暴露给三个客户端的过程,所有这些都来自一个优雅且 Pythonic 的代码库,而无需您考虑任何格式或传输的特殊性。

为了加快客户端检索信息的速度,后端数据将被缓存在 Redis 中,并由作业调度服务定期更新。

使用的数据提供商将是美国财政部的真实长期利率。客户端将是通过 curl 调用的通用基于 HTTP 的客户端,尽管在实践中,任何 HTTP 客户端都可以。

流程和 IRA 服务

目标是使外部客户端应用程序能够轻松高效地访问美国长期利率信息。为此,您将使用 Zato 的几个功能

Zato 鼓励将每个业务流程划分为一组 IRA 服务——也就是说,暴露给用户的每个服务都应该是

  • 有趣性:服务应提供真正的价值,使潜在用户至少会停下来片刻,并考虑在其自己的应用程序中为自身利益使用该服务。

  • 可重用性:使服务模块化将使您能够在尚未预见的情况下使用它们——在较低级别的服务之上构建新的,甚至可能是意想不到的解决方案。

  • 原子性:服务应具有明确定义的目标,从服务用户的角度来看是不可分割的,并且最好服务之间没有功能重叠。

IRA 方法紧密遵循 UNIX 的“做好一件事并做到最好”的哲学以及工程领域广为人知和遵循的 KISS 原则。

当您设计 IRA 服务时,它几乎与在独立应用程序的组件之间定义 API 完全相同。不同之处在于,服务连接在分布式环境中运行的多个应用程序。一旦您考虑到这一点,心理过程是相同的。

任何已经在用任何编程语言编写的单节点应用程序中创建了任何类型的有趣接口的人,在处理 IRA 服务时都会感到宾至如归。

从 Zato 的角度来看,服务对应于 SOA 中的 S 还是 REST 中的 R 没有任何区别;但是,在本文中,我使用的是前一种方法。

规划服务

您需要做的第一件事是绘制集成过程的图表,提取将要实现的服务并记录其用途。如果您需要帮助,Zato 提供了其自己的 API 文档,作为如何记录服务的一个示例(请参阅 https://zato.io/docs/progguide/documenting.htmlhttps://zato.io/docs/public-api/intro.html

  • Zato 的调度器配置为每小时调用一次服务(update-cache)刷新缓存。

  • update-cache 默认情况下获取当月的 XML,但可以配置为获取任何日期的数据。这允许在其他上下文中重用该服务。

  • 客户端应用程序使用 JSON 或简单的 XML 来请求长期利率 (get-rate),并且响应是基于缓存在 Redis 中的数据生成的,这使得它们非常快速。单个 SIO Zato 服务可以生成 JSON、XML 或 SOAP 响应。实际上,同一服务可以独立地在完全不同的通道(例如 HTTP 或 AMQP)中暴露,每个通道使用不同的安全定义,并且不会中断其他通道的消息流。

图 1. 整体业务流程

实现

这两个服务的完整代码在 GitHub 上以 gist 的形式提供,并且只讨论最有趣的部分。

linuxjournal.update-cache

服务执行的步骤是

  • 连接到 treasury.gov。

  • 下载大型 XML。

  • 查找包含业务数据的有趣元素。

  • 将所有内容存储在 Redis 缓存中。

服务的关键片段如下所示。

使用 Zato 服务时,您永远不需要硬编码网络地址。服务屏蔽此类信息并使用人为定义的名称,例如“treasury.gov”;在运行时,这些名称会解析为一组具体的连接参数。这适用于 HTTP 和 Zato 支持的任何其他协议。您还可以在运行时更新连接定义,而无需触及服务代码且无需任何重启。


 1 # Fetch connection by its name
 2 out = self.outgoing.plain_http.get('treasury.gov')
 3
 4 # Build a query string the backend data source expects
 5 query_string = {
 6  '$filter':'month(QUOTE_DATE) eq {} and year(QUOTE_DATE) eq
{}'.format(month, year)
 7 }
 8
 9 # Invoke the backend with query string, fetch 
   # the response as a UTF-8 string
10 # and turn it into an XML object
11 response = out.conn.get(self.cid, query_string)

lxml 是一个非常好的 Python XML 处理库,并在 示例中用于对返回的复杂文档发出 XPath 查询


1 xml = etree.fromstring(response)
2
3 # Look up all XML elements needed (date and rate) using XPath
4 elements = xml.xpath('//m:properties/d:*/text()', 
  ↪namespaces=NAMESPACES)

对于后端服务返回的每个元素,您都在 Redis 缓存中创建一个条目,格式由 REDIS_KEY_PATTERN 指定——例如,linuxjournal:rates:2013:09:03,值为 1.22


 1 for date, rate in elements:
 2
 3   # Create a date object out of string
 4   date = parse(date)
 5
 6   # Build a key for Redis and store the data under it
 7   key = REDIS_KEY_PATTERN.format(
 8       date.year, str(date.month).zfill(2), 
         ↪str(date.day).zfill(2))
 9   self.kvdb.conn.set(key, rate)
10
12   # Leave a trace of our activity
13   self.logger.info('Key %s set to %s', key, rate)

linuxjournal.get-rate

既然更新缓存的服务已准备就绪,那么返回数据的服务非常简单但功能强大,可以完整地重现它


 1 class GetRate(Service):
 2 """ Returns the real long-term rate for a given date
 3 (defaults to today if no date is given).
 4 """
 5 class SimpleIO:
 6     input_optional = ('year', 'month', 'day')
 7     output_optional = ('rate',)
 8
 9 def handle(self):
10     # Get date needed either from input or current day
11     year, month, day = get_date(self.request.input)
12
13     # Build the key the data is cached under
14     key = REDIS_KEY_PATTERN.format(year, month, day)
15
16     # Assign the result from cache directly to response
17     self.response.payload.rate = self.kvdb.conn.get(key)

有几个要点需要注意

  • 使用了 SimpleIO——这是一种声明式语法,用于表示可以序列化为当前 Zato 版本中的 JSON 或 XML 的简单文档,未来版本将推出更多功能。

  • 在服务中,您完全不必提及 JSON、XML 甚至 HTTP。它都在 Python 对象的高级级别上工作,而无需指定任何输出格式或传输方法。

这就是 Zato 的方式。它提倡可重用性,这很有价值,因为通用且有趣的服务(例如返回利率)在无法预测的情况下注定是受欢迎的。

作为服务的作者,您不会被迫承诺使用特定格式。这些都是可以通过多种方式处理的配置详细信息,包括 Zato 提供的 GUI。单个服务可以同时通过多个访问通道暴露,每个通道独立于任何其他通道使用不同的数据格式、安全定义或速率限制。

安装服务

有几种安装服务的方法

  • 从命令行热部署。

  • 从浏览器热部署。

  • 将其添加到 services-sources.txt——您可以指定到单个模块、Python 包或 Python 点式名称的路径以导入它。

让我们从命令行热部署到目前为止您所拥有的内容,假设 Zato 服务器安装在 /opt/zato/server1 中。您可以使用 cp 命令来执行此操作


$ cp linuxjournal.py /opt/zato/server1/pickup-dir
$

现在在服务器日志中


INFO - zato.hot-deploy.create:22 - Creating tar archive
INFO - zato.hot-deploy.create:22 - Uploaded package id:[21],
 ↪payload_name:[linuxjournal.py]

这是刚刚发生的事情

  • 要部署的服务器存储在 SQL 数据库中,并且集群中的每台服务器都收到了新代码部署的通知。

  • 每台服务器都备份了当前部署的服务,并将其存储在文件系统中(默认情况下,会保留最近 100 个备份的循环日志)。

  • 每台服务器都导入了该服务并使其可供使用。

所有这些更改都在整个集群中引入,无需重启和重新配置。

使用 GUI 配置所需的资源

Zato 的 Web 管理员是一个 GUI,可用于快速创建服务所需的服务器对象、检查运行时统计信息或收集调试目的所需的信息。

Web 管理员只是 Zato 自身 API 的一个客户端,因此它所做的一切也可以通过命令行或用户创建的进行 API 调用的客户端来实现。

最重要的是,可以使用基于 JSON 的配置“批量”管理服务器端对象,该配置可以保存在配置存储库中以进行版本控制和差异比较。这允许有趣的工作流程,例如在开发环境中创建基本配置并将其导出到测试环境,在测试环境中可以将新配置合并到现有配置中,稍后,所有这些都可以导出到生产环境。

图 2-6 显示了以下配置

  • 调度器的作业,用于调用更新缓存的服务。

  • 用于连接到 treasury.gov 的传出 HTTP 连接定义。

  • 每个客户端的 HTTP 通道——没有要求为每个客户端提供单独的通道,但这样做允许为每个通道分配不同的安全定义,而不会干扰任何其他通道。

图 2. 调度器作业创建表单

图 3. 传出 HTTP 连接创建表单

图 4. JSON 通道创建表单

图 5. 纯 XML 通道创建表单

图 6. SOAP 通道创建表单

测试它

update-cache 将由调度器调用,但 Zato 的 CLI 提供了从命令行调用任何服务的方法,即使它没有挂载到任何通道上,就像这样


$ zato service invoke /opt/zato/server1 linuxjournal.update-cache
 ↪--payload '{}'
(None)
$

没有输出,因为该服务不产生任何输出。但是,当您检查日志时,您会注意到


INFO - Key linuxjournal:rates:2013:09:03 set to 1.22

现在,您可以使用 curl 和 JSON、XML 和 SOAP 从命令行调用 get-rate。通过三个独立通道暴露的同一服务将生成三种格式的输出,如下所示(为了清晰起见,输出略有重新格式化)。

输出 1


$ curl localhost:17010/client1/get-rate -d 
 ↪'{"year":"2013","month":"09","day":"03"}' 
 ↪{"response": {"rate": "1.22"}}
$

输出 2


$ curl localhost:17010/client2/get-rate -d '
  <request><year>2013</year><month>09</month><day>03</day></request>'
<response>
 <zato_env>
  <cid>K295602460207582970321705053471448424629</cid>
  <result>ZATO_OK</result>
 </zato_env>
 <item>
  <rate>1.22</rate>
 </item>
</response>
$

输出 3


$ curl localhost:17010/client3/get-rate \
    -H "SOAPAction:get-rates" -d '
  <soapenv:Envelope xmlns:soapenv=
↪"http://schemas.xmlsoap.org/soap/envelope/"
      xmlns:z="https://zato.io/ns/20130518">
    <soapenv:Body>
      <z:request>
        <z:year>2013</z:year>
        <z:month>09</z:month>
        <z:day>03</z:day>
      </z:request>
    </soapenv:Body>
  </soapenv:Envelope>'
<?xml version='1.0' encoding='UTF-8'?>
 <soap:Envelope 
  ↪xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
   ↪xmlns="https://zato.io/ns/20130518">
  <soap:Body>
   <response>
    <zato_env>
     <cid>K175546649891418529601921746996004574051</cid>
    <result>ZATO_OK</result>
   </zato_env>
   <item>
    <rate>1.22</rate>
   </item>
  </response>
 </soap:Body>
</soap:Envelope>
$
IRA 是关键

IRA(有趣性、可重用性、原子性)是您在设计要成功的服务时应始终牢记的关键。

本文中介绍的两个服务都符合以下标准

  • I:专注于提供对多方有趣的数据。

  • R:可以参与许多流程,并通过多种方法访问。

  • A:只专注于一项工作并做好它。

在这方面,Zato 使您可以轻松地通过多个通道暴露服务,并将它们整合到更高级别的集成场景中,从而提高它们对潜在客户端应用程序的整体吸引力(IRA 中的 I)。

思考一些不设计服务的方式可能会有所帮助

  • 反-I:update-cache 可以转换为两个较小的服务。一个将获取数据并将其存储在 SQL 数据库中;另一个将从 SQL 中获取数据并将其放入 Redis 中。即使可以通过某些理由来为这种设计辩护,这两个服务对于外部应用程序来说都不会有趣。如果其他系统需要更新缓存,则应创建并向客户端应用程序公开包装这两个服务的第三个服务。换句话说,让我们将实现细节保留在内部,而不要将它们暴露给全世界。

  • 反-R:硬编码重要的参数几乎总是一个糟糕的主意。结果是服务无法由外部系统通过调用一组参数来驱动。例如,创建一个仅限于特定年份的服务会确保其在原始项目之外的有限用途。

  • 反-A:返回先前查询的列表以响应请求可能是某个特定客户端应用程序的需求,但与另一个客户端应用程序的需求相反。在需要复合服务的情况下,不应强制每个客户端都使用它。

设计 IRA 服务就像设计一个好的编程接口,该接口将作为开源库发布,并在最初无法预测的地方使用。

源于实践经验

Zato 不仅关乎 IRA,还关乎编纂具有实践性质的常见管理和编程任务

  • 每个配置文件都自动进行版本控制,并保存在本地 bzr 存储库中,因此始终可以恢复到安全状态。这是完全透明的,不需要任何配置或管理。

  • 在集成项目启动之前,一个常见的需求是提供消息请求和响应形式的用法示例,特别是当某些服务已在平台上可用时。Zato 允许您指定将服务调用的 n 分之一存储以供以后使用,正是为了管理员可以快速满足此类要求。

关于生产环境,经常被问到的两个问题是:1)我的最慢的服务是什么?以及 2)哪些服务最常用?为了回答这些问题,Zato 提供了统计信息,可以通过 Web 管理员、CLI 或 API 访问。数据可以在任意时间段内进行比较,也可以导出为 CSV。

图 7. 示例统计信息

总结

尽管 Zato 是一个相对较新的项目,但它已经是一个轻量级但完整的解决方案,可用于许多集成和后端场景。无论项目的底层集成原则(例如 SOA 或 REST)如何,该平台都可以用于交付易于使用、维护和扩展的可扩展架构。

加载 Disqus 评论