Skip to content

Maltose 组件设计哲学

Maltose 的组件设计不按“轻量/重量级”简单分类,而是分别回答三个问题:组件如何构造、配置从哪里来、生命周期由谁负责。

核心原则:能力与应用装配分离

组件包负责提供具体能力,例如:

  • mcfg 读取和转换配置。
  • mlog 输出结构化日志。
  • mdbmredis 连接外部数据服务。
  • mcache 提供统一缓存接口和 Adapter。
  • mtracemmetric 封装 OpenTelemetry 能力,contrib/observability 负责统一装配和生命周期管理。

frame/m 则是应用使用的统一门面。它通过 frame/mins 将配置读取、具名实例和延迟初始化组合起来:

go
server := m.Server()
config := m.Config()
logger := m.Log()
db := m.DB()
redis := m.Redis()

这些包级入口来自进程默认 Scope。需要隔离应用或测试状态时,可以显式创建 Scope,而不改变底层组件 API:

go
scope := m.NewScope(config)
server := scope.Server()
db := scope.DB()

因此,更准确的描述是:

组件包提供能力,m/mins 负责应用装配;调用方可以选择使用配置驱动的共享实例,也可以直接使用底层构造函数。

三个设计维度

1. 构造方式

组件可能同时提供多种入口:

  • New(...):由调用方显式构造并持有。
  • Instance(name):获取组件包管理的具名实例。
  • m.Xxx(name):从默认 Scope 获取经过应用配置装配的具名实例。
  • scope.Xxx(name):从显式 Scope 获取隔离的具名实例。
  • 包级默认函数:适合简单场景,例如 mcache.Set(...)

这些入口不是互斥的“组件身份”,而是服务不同使用场景。

2. 配置来源

调用方可以直接把 Config 传给 New,也可以让 m/minsmcfg 读取配置后创建实例。

go
// 显式构造:依赖清晰,适合库代码和测试。
db, err := mdb.New(&mdb.Config{Type: "mysql", DSN: dsn})

// 应用装配:配置和具名实例由框架管理。
db = m.DB("report")

m/mins 的价值不是隐藏所有依赖,而是为应用入口提供一致的配置驱动体验。

3. 生命周期归属

  • 调用方通过 New 创建的资源,由调用方负责关闭。
  • 默认 Scope 或显式 Scope 返回的实例由应用统一持有;实现 io.Closer 的资源可通过 m.WithCloser 纳入退出流程,需要 context 的资源使用 shutdown hook。
  • OpenTelemetry Provider 属于进程级全局状态,需要显式初始化并在退出时 shutdown。

为什么 mcfg 特殊?

数据库、Redis、日志等应用组件都可能依赖配置,因此配置解析本身不能反过来依赖这些消费者。mcfg 必须具备独立创建实例的能力:

text
m.Config()
  → mins.Config()
    → mcfg.Instance()

这里的关键不是“配置必须最先完成初始化”,而是:

配置必须能独立、按需初始化,形成应用装配的启动基点。

mcfg 仍然支持 Adapter 和 Hook,配置内容可以来自本地文件、远程配置中心或应用自定义逻辑。

为什么 mcache 不在 m 门面中?

mcache 同时支持包级默认缓存、独立实例和自定义 Adapter:

go
mcache.Set(ctx, "key", value, time.Minute)

local := mcache.New(1000)
redisCache := mcache.NewWithAdapter(redisAdapter)

框架没有规定唯一的 cache.* 配置节点和应用级 Cache 单例,因此没有把它放入 m/mins。这保留了缓存介质和隔离策略的选择权。

这不意味着 mcache 永远轻量:使用 Redis Adapter 时,它同样依赖外部资源和配置。

组件策略对照

组件显式构造包内实例m 门面主要生命周期
mcfgNew / NewWithAdapterInstancem.Config应用级或调用方持有
mlogNewInstancem.Log应用级或调用方持有
mdbNewInstancem.DB外部连接资源
mredisNewInstancem.Redis外部连接资源
mhttpNewmins 管理m.Serverm.App 管理
mcacheNew / NewWithAdapter包级默认缓存调用方决定
mtrace / mmetricProvider 初始化OpenTelemetry 全局 Providerobservability 统一装配进程级,显式 shutdown

mconv 是独立项目,不属于 Maltose 仓库内的组件实例体系。

面向应用与面向库的选择

  • 应用业务代码优先使用 m 门面,减少重复装配。
  • 可复用库代码优先接收显式依赖,不要隐式读取应用全局实例。
  • 单元测试优先注入最小接口,避免直接依赖 m.DB()m.Redis()
  • 同一应用内的多个用途优先使用具名实例;多应用或测试边界使用 m.NewScope(config);底层库仍可直接构造并自行管理生命周期。

这套设计追求的不是“所有组件使用完全相同的内部实现”,而是让应用入口一致,同时保留底层组件的独立可用性。

Released under the MIT License.