是否可以修改在远程服务器上作为 Spring 组件运行的 Clojure 代码并使用 REPL 下载修改后的代码?

问题描述

假设我有以下设置:

  1. 一个 Spring Boot 应用程序。
  2. 里面有一个 Camunda 工作流引擎
  3. 该应用程序中有多个组件 (@org.springframework.stereotype.Component),它们是用 Clojure 编写的,供 Camunda 工作流引擎使用。

Setup: Spring Boot application with Camunda engine and multiple components inside it

我听说据称可以在不重新启动的情况下修改 Clojure 应用程序的代码

所以,我想

  1. 修改这些组件的代码(无需重新启动应用程序),
  2. 向应用程序添加新组件(无需重新启动应用程序),以及
  3. 在我完成原型设计后,在那里下载所有组件的当前版本。

这个想法是我从 REPL 对组件进行原型设计,直到它们按设计工作。这意味着 Camunda 工作流引擎将使用我在 REPL 上的操作修改的组件。

然后,我下载应用程序中组件的当前版本(以便在应用程序关闭时它们不会丢失)。此代码随后会被清理、重构、由单元测试覆盖并置于版本控制之下。

问题:

  1. 理论上是否可以使用 Clojure 实现这样的工作流程(不一定是开箱即用的)?
  2. 是否有任何已知的限制会使这种工作流程完全不可能实现?

更新 1

发现以下项目表面上允许您使用 REPL 与 Java 代码交互:

  1. spring-boot-bugger
  2. spring-repl

但是,我不知道您是否可以使用它们来更改代码

解决方法

这是一个非常粗略和简单的示例,说明如何执行此操作。我有 将 Camunda 放在这里是因为我认为它实际上并不相关。

这里的方法:

  • 创建 Spring 服务,委托给实际的 Clojure 代码。
  • 确保将该代码的“当前良好版本”加载到您的 过程
  • 启动“服务器 REPL”以允许覆盖

可以找到完整的项目here

提供一个服务,委托给一些 Clojure 代码:

@Service
class ClojureBackedService {
    BigDecimal add(BigDecimal a,BigDecimal b) {
        Clojure.var('net.ofnir.repl','add').invoke(a,b)
    }
}

启动 REPL 并加载一些“良好的初始设置”:

@Service
class ClojureRepl {

    @PostConstruct
    def init() {
        def require = Clojure.var('clojure.core','require')
        require.invoke(Clojure.read('net.ofnir.repl'))
        Clojure.var('clojure.core.server','start-server').invoke(
                Clojure.read("{:port 5555 :name spring-repl :accept clojure.core.server/repl}")
        )
    }

    @PreDestroy
    def destroy() {
        Clojure.var('clojure.core.server','stop-server').invoke(
                Clojure.read("{:name spring-repl}")
        )
    }

}

为了展示它是如何工作的,提供一个 web 端点,它增加了两个 数字:

@RestController
class MathController {
    private final ClojureBackedService backend

    MathController(ClojureBackedService backend) {
        this.backend = backend
    }

    @PostMapping
    def add(BigDecimal a,BigDecimal b) {
        backend.add(a,b)
    }
}

运行应用程序并进行第一次测试:

$ curl -da=5 -db=5 localhost:8080                                     
10

看起来不错。现在连接到 REPL 并替换 add 函数:

$ telnet localhost 5555
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
user=> (ns net.ofnir.repl)
nil
net.ofnir.repl=> (defn add [a b] (* a b))
#'net.ofnir.repl/add

不再调用端点:

$ curl -da=5 -db=5 localhost:8080
25

然后你就在运行时改变了你的 spring 服务。

,

如果您需要更新作为例如一部分执行的代码无需重新启动流程引擎的服务任务,那么您可能需要考虑将流程引擎与此代码完全解耦。外部服务任务模式允许您这样做: https://docs.camunda.org/manual/latest/user-guide/process-engine/external-tasks/

不是将代码部署到 Camunda 运行时,而是在单独的运行时(例如 JVM)中启动外部工作程序。一些好处包括:

  • 流程引擎和工作线程是解耦的。引擎不需要知道工人在哪里。工作人员在引擎可用时调用引擎。
  • 线程管理被外化为工作线程。引擎线程池不需要扩容。
  • worker 可以用所选的编程语言实现
  • worker 可以独立升级(即使不同版本的 worker 可以同时运行,例如在滚动升级期间)
  • worker 可以独立于流程引擎进行扩展

另见例如: