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

Is it possible to modify Clojure code running as a Spring component on a remote server and download the modified code using the REPL?

假设我有以下设置:

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

我听说据说可以在不重新启动 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}")
        )
    }

}

为了展示它是如何工作的,提供一个网络端点,它添加了两个 号码:

@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)中启动一个外部工作者。一些好处包括:

  • 流程引擎和工人解耦。 Engine 不需要知道 worker 的位置。工作人员在可用时调用引擎。
  • 线程管理外化到 worker。引擎线程池不需要扩容
  • worker 可以用选择的编程语言实现
  • worker 可以独立升级(甚至不同版本的 worker 也可以同时 运行,例如在滚动升级期间)
  • worker 可以独立于流程引擎进行扩展

另见实例: