ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Metabase 驱动开发实战:用 Multimethods 扩展数据库驱动的核心机制详解

Metabase 驱动开发实战:用 Multimethods 扩展数据库驱动的核心机制详解 Metabase 驱动开发实战用 Multimethods 扩展数据库驱动的核心机制详解【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase本文为 Metabase 驱动开发者讲解如何基于 multimethods.md 的官方指引利用 Clojure 多方法multimethods机制为特定数据库实现驱动你将理解驱动就是一个 keyword这一核心设计掌握驱动命名空间组织、方法注册、父驱动继承与get-method调用的正确姿势并能用仓库自带的driver-methods命令与:drivers-devREPL 高效完成驱动开发调试。一、核心设计驱动就是一个 keyword方法靠多方法分派Metabase 的驱动体系建立在一个极简的抽象上一个 Metabase 驱动本质上就是一个 keyword如:foxpro98不存在类或对象。所有驱动行为都通过 metabase.driver 命名空间 中定义的 多方法 实现驱动只需要为这些多方法提供defmethod实现分派值就是驱动自己的 keyword;; 定义驱动的命名空间 (ns com.mycompany.metabase.driver.foxpro98 (:require [metabase.driver :as driver])) ;; 为 driver/display-name 多方法提供 :foxpro98 的实现 (defmethod driver/display-name :foxpro98 [_] Visual FoxPro 98)这种设计带来两个直接好处零侵入扩展你可以直接复用 Metabase 现有的驱动代码父驱动实现只为自己的数据库补上缺失的方法无 OOP 负担没有类层次对象的实例化问题继承关系由 keyword hierarchy 表达。从源码看这一机制落在 metabase.driver.implregister!把驱动 keywordderive到根节点:metabase.driver/driver以及::concrete若为具体驱动抽象父驱动则不 derive 到::concrete从而在管理界面隐藏、禁止直接查询。多方法分派统一使用:hierarchy #hierarchy因此子驱动会自动继承父驱动的方法实现——这正是父驱动能力的来源。二、驱动命名空间的组织方式每个驱动一个独立命名空间以 Fox Pro 98 为例驱动的主体代码位于com.mycompany.metabase.driver.foxpro98命名空间。核心 Metabase 驱动统一放在metabase.driver.driver-name命名空间下如 MySQL 驱动、Postgres 驱动、SQLite 驱动。第三方插件驱动建议使用符合 Java 包命名规范的前缀如com.mycompany.metabase.driver.foxpro98。从源码结构看metabase.driver.impl 中的driver-expected-namespace函数规定了核心驱动的命名空间推断规则若 driver keyword 没有命名空间则期望位于metabase.driver.name带命名空间的 keyword如插件驱动则期望使用完整路径。load-driver-namespace-if-needed!会在需要时按此规则惰性require对应命名空间。大型驱动进一步拆分子命名空间对于较大的驱动常见做法是把复杂逻辑拆分到独立的子命名空间query-processor子命名空间如com.mycompany.metabase.driver.foxpro98.query-processor包含把 MBQL 查询用 Metabase 图形化查询构建器创建的查询转换成原生查询如 SQL的逻辑。查询处理器通常是驱动中最复杂的部分独立成命名空间便于维护。可以参考仓库内的 metabase.driver.sql.query-processor 与 metabase.driver.sql-jdbc 下的子目录 的组织方式——sync、execute、connection、metadata等均拆分为独立命名空间sync子命名空间包含 Metabase 数据库同步database synchronization 所调用方法的实现。三、实现 metabase.driver 中的多方法metabase.driver 命名空间 定义了一系列多方法驱动为其提供实现如上面的driver/display-name示例。驱动的全部核心功能都由多方法承载分派值即驱动 keyword示例中为:foxpro98。阅读 src/metabase/driver.clj 可以浏览完整的多方法清单。每个方法的 docstring 都说明了用途与调用时机你需要逐个阅读并判断是否需要实现——大多数方法都是可选的且很多方法提供了:default实现作为兜底。例如;; 显示名的默认实现直接首字母大写驱动名 (defmethod display-name :default [driver] (str/capitalize (name driver)))从源码看分派函数分为两类这决定了你实现的方法会被初始化的时机dispatch-on-uninitialized-driver仅把参数转为 keyword 并加载其命名空间the-driver不触发初始化。适合display-name、connection-properties这类只返回驱动信息、不连库的平凡方法dispatch-on-initialized-driver分派前保证驱动已完成初始化the-initialized-driver适合需要真正连接数据库的方法如can-connect?、dbms-version等。四、用 driver-methods 命令快速列出所有可实现的驱动多方法在开发驱动时最实用的入门动作是列出所有可供实现的多方法。在仓库根目录执行clojure -M:run driver-methods该命令会打印所有驱动命名空间及其多方法清单包括sql、sql-jdbc多方法以及测试扩展多方法。命令实现位于 src/metabase/cmd/driver_methods.clj它遍历系统 classpath 上所有以metabase.driver或metabase.test.data开头的命名空间筛出clojure.lang.MultiFn类型的公共 var 并打印签名命令入口driver-methods定义在 src/metabase/cmd/core.clj 中。如果还想同时看到各方法的 docstring判断是否该实现的最快途径clojure -M:run driver-methods docs在 deps.edn 中可以确认-M:run的运行别名配置clojure -M:run:drivers可以额外把模块内所有驱动纳入 classpath。五、驱动初始化initialize! 何时执行、写什么所有驱动都可以通过实现metabase.driver/initialize!提供一段只执行一次的初始化代码。它在 Metabase 初始化该驱动时执行——即驱动第一次建立数据库连接之前事实上Metabase 正是用initialize!来驱动惰性加载。典型用途是分配资源或设置某些系统属性。从 src/metabase/driver.clj 的initialize!docstring 与 metabase.driver.impl 的实现看有几个关键约束值得注意不要直接调用initialize!它在首次调用非平凡驱动方法时被自动触发一次且仅一次。initialized?用原子集合记录状态子驱动不会继承父驱动的初始化状态父驱动会被先行初始化initialize-if-needed!在初始化某驱动前会先递归初始化 hierarchy 中的全部父驱动并用全局锁保证同一驱动不被并发初始化惰性加载的驱动通常无需自己写initialize!以插件形式打包的惰性加载驱动v1.0 之后的大多数核心驱动即此类会在插件清单plugin manifest中声明init-steps由框架自动生成initialize!实现。手写实现时也无需手动调用父实现框架会代为处理initialize!是唯一不使用驱动 hierarchy 分派的多方法——源码注释明确说明这是为了避免父驱动实现被子驱动重复继承执行。六、父驱动把公共实现沉淀为基类许多驱动共享实现细节如果每个驱动都完整实现同步、连接等逻辑会产生大量重复代码。因此Metabase 把许多高层功能部分或完全实现在共享的父驱动中最常见的父驱动是:sql-jdbc。可以把父驱动理解为面向对象编程中的父类。父驱动的定义方式是在 插件清单plugin manifest 中声明核心驱动则在代码中用register!声明例如 metabase.driver.sql-jdbc 中的(driver/register! :sql-jdbc, :parent :sql, :abstract? true)从 register! 的源码 可见:parent支持传单个 driver 或父驱动集合u/one-or-many parent归一化并会先加载父驱动命名空间、校验抽象驱动不能继承具体驱动等约束。三个重要的父驱动:sql-jdbc可作为拥有 JDBC 驱动的 SQL 数据库的父驱动。它实现了四大核心功能的大部分逻辑你只需实现metabase.driver.sql-jdbc.*命名空间下的sql-jdbc多方法以及metabase.driver.sql.*命名空间下的一些方法。例如使用:sql-jdbc作为父驱动后你就不需要自己实现driver/execute-prepared-statement!这类方法:sql:sql-jdbc的父驱动面向没有 JDBC 驱动的 SQL 数据库如 BigQuery。它实现了相当一部分驱动功能你只需实现metabase.driver.sql.*命名空间下的一些方法见 metabase.driver.sql其中 第 76 行 可见:sql对driver/mbql-native的实现以具体驱动作为父驱动例如:redshift以:postgres为父驱动只提供覆盖 Postgres 行为所需的方法实现。调用父驱动实现get-method用get-method获取父驱动对某个方法的实现并调用等价于面向对象中的super.someMethod()(defmethod driver/mbql-native :bigquery [driver query] ((get-method driver/mbql-native :sql) driver query))metabase.driver命名空间顶部注释也给出了同样的官方范例;; (driver/register-driver! :my-driver, :parent :sql-jdbc) ;; (defmethod driver/describe-table :my-driver [driver database table] ;; (- ((get-method driver/describe-table :sql-jdbc) driver database table) ;; (update :tables add-materialized-views)))必须原样传递driver参数给父实现这样父实现内部调用的其他多方法才会继续用当前驱动的实现。有两种应避免的调用方式;; BAD! 硬编码 :sql —— 若 :sql 的 mbql-native 实现内部调用其他方法 ;; 不会走 :bigquery 的实现而是走 :sql 的实现 (defmethod driver/mbql-native :bigquery [_ query] ((get-method driver/mbql-native :sql) :sql query));; BAD! 硬编码 :bigquery —— 若将来有人以 :bigquery 为父驱动创建新驱动 ;; :sql 实现内部调用的方法会错误地走 :bigquery 的实现而非那个新驱动的定制实现 (defmethod driver/mbql-native :bigquery [_ query] ((get-method driver/mbql-native :sql) :bigquery query))正确做法始终是接收到的driver参数直接透传这一点在 src/metabase/driver.clj 的命名空间注释中被明确强调Make sure to pass along thedriverparameter as when you call other methods。多父驱动多重继承细心的读者可能注意到 BigQuery 同时有:sql和:google两个父驱动。多重继承是被允许且有益的注册方式如下(driver/register! :bigquery, :parent #{:sql :google})当两个父驱动都为某个方法提供了实现时歧义解决办法是为你的驱动直接提供该方法的实现并按上文get-method的方式把你偏好的父驱动实现作为调用目标。对于以插件形式打包的驱动则应在插件清单中注册方法。七、从 REPL 与 CIDER 中开发驱动如果每次改动都要本地安装metabase-core并构建驱动 uberjar再反复重装测试那开发体验会非常糟糕。幸运的是仓库的 deps.edn 提供了:drivers-dev别名可以把一切当作一个巨型工程来运行。启动开发 REPLclojure -A:dev:drivers:drivers-dev修改驱动代码后需要重新构建驱动、把它安装到./plugins目录并重启 Metabase 使改动生效。deps.edn中同时给出了clojure -X:dev:drivers:drivers-dev:test等测试入口EE 版本加:ee:ee-dev可在开发驱动的同时运行驱动相关测试测试方法参见 driver-tests.md。八、延伸阅读与关键文件索引资源路径本文依据的官方文档docs/developers-guide/drivers/multimethods.md驱动多方法定义含全部defmultisrc/metabase/driver.clj驱动注册 / hierarchy / 初始化实现src/metabase/driver/impl.clj:sql父驱动src/metabase/driver/sql.clj:sql-jdbc父驱动src/metabase/driver/sql_jdbc.cljdriver-methods命令实现src/metabase/cmd/driver_methods.clj驱动基础入门docs/developers-guide/drivers/basics.md插件清单声明父驱动等docs/developers-guide/drivers/plugins.md驱动测试docs/developers-guide/drivers/driver-tests.md要点回顾Metabase 驱动 一个 keyword 若干defmethod用clojure -M:run driver-methods docs查清单、读 docstring 决定实现范围尽量以:sql-jdbc/:sql或具体驱动为父驱动复用公共实现调用父实现时务必透传driver参数用:drivers-devREPL 迭代开发配合./plugins目录完成插件驱动的端到端验证。【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表