【问题标题】:Shall I Make my REST Gateway a Library?我应该让我的 REST 网关成为一个库吗?
【发布时间】:2017-02-17 10:39:46
【问题描述】:

假设有一个带有多个客户端的 REST 服务。每个客户端调用 REST 服务都要做大致相同的事情:构造 URL、发送请求、反序列化响应、将 HTTP 返回码映射到异常等。

这似乎是重复代码,因此将 REST 网关代码放入可重用库中可能是个好主意。在 Java 中,这可能如下所示:

  • 有一个依赖于 Jersey 并包含其余客户端代码的 jar 文件。
  • 其余客户端是一个简单的类或 CDI bean。
  • 所有客户端都只是依赖这个 jar 并调用上述类的成员。

因此,客户端无需担心 REST,只需调用方法即可。

这是个好主意吗?

(此问题源于this other question

【问题讨论】:

    标签: java rest dependencies


    【解决方案1】:

    这会给你带来依赖问题。这是一个非常典型的依赖问题,不受 REST 或 Jersey 的约束。但是让我们看看下面的场景:

    依赖关系

    假设有两个服务器(S1 和 S2)、两个客户端(C1 和 C2)和两个库,其中包含用于访问服务器(L1 和 L2)的其余客户端代码。两个客户端都需要查询两个服务器,因此调用结构如下所示:

    C1 ---> L1 ---> S1
      \   ^
       \ /
        X
       / \
      /   v
    C2 ---> L2 ---> S2
    

    此外,L1 和 L2 都依赖于 Jersey。此外,容器(也许您正在 Glassfish 或 Wildfly 应用服务器上运行您的应用程序)依赖于 Jersey(或至少依赖于 jax-rs API [1])。

    C1的简化依赖结构如下:

                  C1
                /  | \
             /     |   \
           <       v     >
    Container     L1       L2
       |           |        |
       v           v        v
    Jersey      Jersey    Jersey
    

    只要三个版本的泽西都一样,一切都很好。没问题。但如果版本不同,您可能会遇到讨厌的NoClassDefFoundErrors、MethodNotFoundErrors 等 [2]。

    刚性结构

    如果版本足够相似,您将不会直接遇到这些错误。也许它会工作很长一段时间。但随后可能会发生以下情况:

    • 您想要更新您的容器。这会更新可能不兼容的 Jersey 的容器版本。 ==> 繁荣。因此,使用 L1 和 L2 会阻止您进行更新。只要 L1 和 L2 没有更新,您就会被旧版本卡住。
    • L2 已更新为新版本的 Jersey。只有在 L1 和您的容器也更新时,您才能使用它。所以你坚持旧版本(你可以这样做,因为 REST 的松散耦合)。但随后 S2 中添加了新功能,该功能仅适用于新版本的 L2,您再次陷入困境。

    请记住,这些错误可能会发生也可能不会发生。不能保证你会遇到麻烦,也不能保证它会起作用。这是一种风险,一颗定时炸弹。

    另一方面,这是一个只有两个服务和两个客户端的简单示例。当然,如果数量更多,风险就会增加。

    稳定依赖原则

    依赖项应遵守Stable Dependencies Principle (SDP),其内容为“依赖于稳定方向”。 Bob 大叔使用传入和传出依赖的数量来定义它,但 SDP 可以从更广泛的意义上理解。

    当我们查看给定的依赖结构并将 Jersey 替换为 guava 时,与 SDP 的联系变得显而易见。这个问题在番石榴中很常见。问题是 Guava 通常不向下兼容。这是一个相当不稳定的库,可以在应用程序中使用(它是不稳定的),但你不应该在可重用的库中使用它(它应该是稳定的)。

    对于泽西岛,这并不那么明显,因为您可能认为泽西岛相当稳定。事实上,就 Bob Martin 的定义而言,它是非常稳定的。但 L2 也可能相当稳定。也许它是由另一个团队开发的,由某个离开公司但没人敢碰它的人开发,因为去年有人试图这样做,导致 C2 团队出现依赖性问题。也许 L2 也很稳定,因为开发 C1 和 L2 的团队的经理之间存在一些持续的争吵,所以 L2 经理声称没有资源、没有预算或其他任何东西,所以只能在明年年底更新 L2。

    因此,根据您的代码库、组织结构等,库可能比您想要的更稳定。

    例外与补救措施

    • 有些语言具有“孤立的依赖关系”,这意味着您可以在同一个应用程序中拥有同一个库的多个版本。
    • 您可以重新打包 Jersey(复制源代码,更改包名称并自己维护)。然后,您可以在版本 X 中使用普通的 Jersey,在版本 Y 中使用重新打包的 Jersey 版本。这可行,但当然这有其自身的问题。工作量很大,而且您必须维护不是您编写的软件。
    • 如果所有内容都在同一个团队中开发,则问题可以忽略不计。然后你就可以控制整个事情,而不需要依赖其他团队做某事。然而,一旦您的代码库增长,更新到新版本可能会很麻烦,因为您不能再逐步进行,但您必须一次将许多服务移动到新版本。
    • 像 maven 这样的构建工具可以让您对所依赖的版本进行一些控制。您可以考虑在 L1 和 L2 中标记 Jersey 依赖项 optional,在 C1 和 C2 中标记 provided。然后它不包含在您的 war 文件中,因此只有一个 Jersey 版本(容器中的那个)。只要此版本与接口兼容,它就可以工作 [3]。

    该怎么做

    我建议将您的表示类(即 DTO)放入客户端 jar 中,让每个客户端决定使用哪个库来进行 REST 调用。通常,那些REST gateways 非常简单,在应用程序之间共享它们通常不值得冒险。至少在不知不觉中遇到这里提到的问题之前考虑一下。

    [1] 为了使解释简单,让我们忽略 Jersey 和 jax-rs API 之间的区别。论据是一样的。

    [2] 实际上它有点复杂。通常,您的构建工具(maven 等)将选择一个版本的 Jersey 并将其放入 war 文件中。另一个版本的 Jersey 与另一个版本“因冲突而省略”。只要版本兼容,就可以。因此,您最终会得到两个版本,一个在您的容器中,一个在您的 war 文件中。前一种情况下的接口兼容性应该足够了,剩下的两个版本就不够了。

    [3] 因为只有一个版本,所以不会出现[2]中提到的问题。接口兼容性问题当然存在。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-18
      • 1970-01-01
      • 2020-09-18
      • 1970-01-01
      • 1970-01-01
      • 2010-09-26
      • 1970-01-01
      相关资源
      最近更新 更多