Clojure 建立多个数据库连接

Clojure establishing multiple database connections

我们有一个 Clojure Web 应用程序,供多个项目 (>20) 使用,这些项目有多个用户同时登录。所有项目都有自己的 MySQL 数据库。我们试图找出一种方法来使用一个应用程序实例来处理来自用户的请求,这些请求是从他们的项目数据库中传递的。

下面的脚本展示了我们多连接的原理,应该可以在 REPL 中执行(正确的数据库设置)。

(ns testmultiple.core
  (:require
    [clojure.java.jdbc :as jdbc]
    [compojure.core :refer [defroutes GET ANY routes context]]
    [conman.core :as conman]
    [mount.core :refer [defstate]]))

(def database-urls {:DB1 "jdbc:mysql://localhost:3306/DB1?user=DB1_user&password=DB1_password"
                    :DB2 "jdbc:mysql://localhost:3306/DB2?user=DB2_user&password=DB2_password"})

;; Connects to all databases in pool-specs
(defn connect!
  [pool-specs]
  (reduce merge (map (fn [pool-spec]
                       {(keyword (key pool-spec)) (conman/connect! {:jdbc-url (val pool-spec)})}) pool-specs)))

;; Disconnect from all databases in db-connections
(defn disconnect!
  [db-connections]
  (map (fn [db] (conman/disconnect! (val db))) db-connections))

;; Establish connections to all databases
;; and store connections in *dbs*
(defstate ^:dynamic *dbs*
          :start (connect!
                   database-urls)
          :stop (disconnect! *dbs*))

;; Bind queries to *db* dynamic variable which is bound
;; to each clients database before executing queries
;; The queries file defines the query get-user which
;; returns user by user id
(def ^:dynamic *db* nil)
(conman/bind-connection *db* "sql/queries.sql")

(mount.core/start)

; Define function that executes in current *db* binding
(defn getuser [id] (get-user {:id id}))

; Works, the user with Id 670 is returned from DB1
(with-bindings {#'*db* (:DB1 *dbs*)} (getuser 670))

; Works, the user with Id 670 is returned from DB2
(with-bindings {#'*db* (:DB2 *dbs*)} (getuser 670))

更具体地说,项目是从路由器中的 URL 请求推断出来的。以下代码显示了路由器的原理。访问 www.example.com/DB1/page1 和 www.example.com/DB2/page2 将分别显示包含来自 DB1 的数据的 page1 和包含来自 DB2 的数据的 page2。

(defn serve-page1 [] (str "page1" (getuser 670)))
(defn serve-page2 [] (str "page2" (getuser 670)))

(def home-routes
  (context "/:project" [project]
    (if (contains? *dbs* (keyword project))
      (routes
        (GET "/page1" []
          (with-bindings {#'*db* ((keyword project) *dbs*)}
            (serve-page1)))
        (GET "/page2" []
          (with-bindings {#'*db* ((keyword project) *dbs*)}
            (serve-page2))))
      (ANY "*" [] (str "Project not found")))))

这将是一个具有相当大流量的应用程序。值得注意的是,我们仍处于开发阶段,因此无法在本地主机上使用多个数据库 运行 来测试此解决方案。我们的问题是

Is establishing multiple connections like this reasonable, stable and scalable?

是的,这是一个非常合理的做法。很少有数据库系统受传出连接数的限制。 JDBC 和 Korma 都可以在 clojure 中很好地处理这个问题。当然,在构建监控和操作相关组件时,您确实需要知道哪些请求依赖于哪个数据库。这样您就可以知道是哪个数据库导致了问题。

Are there other better methods for the routing and dynamic binding of the project's database?

我唯一的建议是明确地将数据库传递给每个函数而不是使用绑定,尽管这是个人风格意见,您的方法显然会奏效。