【问题标题】:Apache Camel: how to isolate logic into testable atomic routesApache Camel:如何将逻辑隔离到可测试的原子路由中
【发布时间】:2020-07-24 13:24:21
【问题描述】:

让我们考虑以下用例:

  • 一组提供者将数据推送到本地服务器上的相应目录中(例如,P1 将数据推送到 data/P1,P2 推送到 data/P2 等)
  • 每个提供者都有自己的生成规则(例如 P1 生成纯 txt 文件,P2 生成档案,P3 生成加密文件等)
  • 在服务器上运行的 Spring Boot 应用程序上,每个提供程序都有自己的 Camel 路由,每 10 分钟从相应目录读取一次(例如,R1 读取自(“file:data/P1”),R2 读取自( “文件:数据/P2”), 等)
  • 给定的提供者还可以组合规则(例如 P4 生成包含加密数据的档案)
  • 根据路径,读取的数据会被相应地处理,以便将纯 txt 文件移动到目标目录(例如 R2 解压缩数据并移动它,R4 解压数据,解密提取结果并移动它等)

一旦实现了更多的路由,很明显大部分代码是重复的并且可以被提取;事实上,由于可以组合规则,每个数据细化都可以看作是一个原子操作,可用于给定的路由。 例如,让我们考虑以下原子操作:

  • 解压
  • 解密
  • 移动

所以,这些路线的外观如下:

R1

from("file:data/P1")
  .to("file:destination")

R2

from("file:data/P2")
  // UNZIP LOGIC HERE
  .to("file:destination")

R3

from("file:data/P3")
  // DECRYPT LOGIC HERE
  .to("file:destination")

R4

from("file:data/P4")
  // UNZIP LOGIC HERE
  // DECRYPT LOGIC HERE
  .to("file:destination")

由于我想提取通用逻辑,我在这里看到两个主要选项(带有相应的 R4 生成代码):

  • 将逻辑提取到自定义组件中
from("file:data/P4")
  // FOR EACH FILE
  .to("my-custom-component:unzip")
  .to("my-custom-component:decrypt")
  .to("file:destination")
  • 将逻辑提取到更小的路由中
from("file:data/P4")
  // FOR EACH FILE
  .to("direct:my-unzip-route")
  .to("direct:my-decrypt-route")
  .to("file:destination")

(当然,这是一个超级简化,但这只是为了给你一个大局。

在这两个选项之间,我更喜欢后者,它可以让我快速重用 Camel EIP(例如 unmarshal().pgp()):

from("file:data/P4")
  .to("direct:my-unzip-route")
  .to("direct:my-decrypt-route")
  .to("file:destination");

from("direct:my-unzip-route")
  // LOGIC
  .unmarshal().zip()
  // MORE LOGIC
  ;

from("direct:my-decrypt-route")
  // LOGIC
  .unmarshal().pgp()
  // MORE LOGIC
  ;

第一个问题:由于给定的子路由改变了原始文件集(例如,解压缩可以将一个存档转换为 100 个文件),使用 enrich() 而不是 to()?

第二个问题:我应该在这些子路由之间路由什么?现在,由于为简单起见,这里没有解释实现细节,我正在路由一个文件名的集合,所以,我有一个 from("file:data/P4") from("direct:read-P4") 接收文件名列表作为输入,然后将该列表传播到给定的子路由;每个子路由,从文件名列表开始,应用自己的逻辑,生成新文件并返回带有更新列表的主体(例如,接收 {"test.zip"} 并返回 {"文件 1.txt”、“文件 2.txt”})。 因此,给定的子路由如下所示:

from("direct:...")
  // FOR EACH FILE NAME
    // APPLY TRANSFORMATION LOGIC TO THE CORRESPONDING FILE
  
  .setBody( // UPDATED LIST OF FILE NAMES )

在没有生产者 EIP 的情况下结束路由是否正确?还是我应该用生成给定新文件的 to() 来结束它?如果是这样,下一个子路由必须从同一个目录再次读取所有数据,这似乎并没有那么优化,因为我已经知道必须考虑哪些文件。

第三个问题:假设让给定的子路由转换数据并返回对应的名称列表是可以的,我应该如何测试呢?不可能模拟结束的生产者,因为我没有与生产者一起完成路线......所以,我必须使用拦截器吗?或者还有什么?

基本上,我提出这个问题是因为我有一组完美的工作路线,但是在创建测试期间,我注意到其中一些是不自然的测试......这很可能是一个结果错误的设计。

【问题讨论】:

    标签: apache-camel atomic


    【解决方案1】:
    1. 如果您想要/需要将外部数据集成到您的路线中,则使用丰富
    2. 下面的示例说明了如何从外部文件加载文件类型的实际列表,其中聚合策略会将文件类型添加到交换标头。之后,对丰富的标头执行拆分,并将交换定向到相应的路由(如果没有可用于文件类型的路由,则会引发错误)。此路由使用enrich EIP、split EIP 和content based routing。
    from(...)
        .enrich("file:loadFileList", aggregationStrategy)
        .split(header("fileList").tokenize(","))
        .to("direct:routeContent");
    
    from("direct:routeContent")
        .choice()
            .when(header("fileList").isEqualTo("..."))
                .to("direct:storeFile")
            .when(header("fileList").isEqualTo("..."))
                .to("direct:unzip")
            .when(header("fileList").isEqualTo("..."))
                ...
            .default()
                .throw(...)
        .end()
    
    from("direct:storeFile")
        .to("file:destination");
    
    from("direct:unzip")
        .split(new ZipSplitter())
            .streaming().convertBodyTo(String.class)
            .choice()
                .when(body().isNotNull())
                    .to("file:destination")
                .default()
                    .throw(...)
            .end()
        .end()
    

    相应的聚合策略可能如下所示:

    public class FileListAggregationStrategy implements AggregationStrategy {
      
        @Override
        public Exchange aggregate(Exchange original, Exchange resource) {
            String content = resource.getIn().getBody(String.class);
            origina.setHeader("fileList", content);
        }
    }
    

    但是请注意,我自己没有测试过代码。我只是在脑海中写下了一个基本结构。

    在没有生产者EIP的情况下结束路由是否正确

    AFAIK Camel 应该“告诉您”(在错误的意义上)当您尝试加载路线时没有可用的生产者。但是,如果我没记错的话,一个简单的.log(LoggingLevel.DEBUG, "...") 就足以阻止 Camel 抱怨(现在已经有一段时间没有使用 Camel 了)。

    1. 您始终可以使用AdviceRouteBuilder 编织某些路线并根据您的需要修改路线。拦截器本身也作用于某些路由定义,例如.to(...),即测试路由的最简单方法是使用MockEndpoints 并将最终的生产者调用(即.to("file:destination"))替换为您的模拟端点并在其上执行您的断言. 在上述示例中,您可以即仅通过提供ProducerTemplate 和相应的 ZIP 存档作为正文来测试最终解压缩路径,并将.to("file:destination") 替换为您对其执行断言的模拟端点。您还可以发送单个 ZIP存档到direct:routeContent 并传递带有各自名称的fileList 标头,以便路由应该调用解压缩路由。在这里,您可以重用上面修改过的direct:unzip 路由定义,从而检查存档是否可以解压缩,或者您可以将 .to("direct:unzip") 路由调用替换为其他模拟端点等。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-07-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-07
      • 2018-07-29
      • 2016-09-29
      相关资源
      最近更新 更多