RRuna

业务模块

用 Module 组织业务入口

Module 是应用业务层的入口。用户、订单、支付、后台管理、数据同步,都可以各自做成一个 Module。

它和 Install(Provider) 很像:都会进入 Runa 的生命周期,也都能注册命令、Host、路由和读取 DI。区别是职责不同:Install 装框架能力,Module 装你的业务。

Module 和 Install 有什么区别

入口 主要用途 常见内容
app.Install(...) 安装框架能力 route、cache、queue、database、storage
app.Module(...) 装载业务模块 user、order、billing、admin

业务模块通常不创建新的框架能力,而是组合已经安装的能力:注册路由、命令、队列任务、事件监听器或定时任务。

先定义一个业务模块

package user

import (
    "context"

    "github.com/duxweb/runa/provider"
    "github.com/duxweb/runa/route"
)

type Module struct {
    provider.ModuleBase
}

func (Module) Name() string { return "user" }

func (Module) Register(ctx context.Context, app provider.Context) error {
    route.Default().Get("/users", listUsers)
    return app.RegisterCommand(UserSyncCommand{})
}

provider.ModuleBase 提供空生命周期方法。你只需要实现当前模块用到的阶段。

上面这个模块做了两件事:注册 /users 路由,注册 UserSyncCommand 命令。

把模块注册进应用

app := runa.New()
app.Install(
    route.Provider(route.Addr(":8080")),
    cache.Provider(),
)

app.Module(UserModule{})

if err := app.Run(context.Background()); err != nil {
    panic(err)
}

根门面也提供同级入口:

runa.Install(route.Provider(route.Addr(":8080")))
runa.Module(UserModule{})

Module 里可以注册什么

Module 拿到框架上下文后,可以注册业务侧需要的内容:

  • RegisterCommand(...) 注册业务命令
  • RegisterHost(...) 注册业务后台 Host
  • provider.MustInvoke[T](app) 读取已经注册到 DI 的能力
  • route.Default().Get(...) 注册 HTTP 路由

命令、路由、队列任务这类“声明式注册”建议放在 Register 阶段。

Module 也有 Shutdown

如果你的业务模块打开了连接、启动了后台协程,或者持有需要释放的资源,可以实现 Shutdown

func (Module) Shutdown(ctx context.Context, app provider.Context) error {
    return nil
}

普通业务模块通常不需要自己管理资源。数据库、缓存、队列这类能力应该交给对应能力包和 DI 管理生命周期。

模块依赖顺序

Module 可以声明依赖,让业务模块按依赖顺序执行生命周期:

func (Module) Depends() []string {
    return []string{"auth"}
}

如果依赖缺失或出现循环依赖,应用启动会失败。

什么时候用 Module

  • 业务域需要自己的路由、命令、任务和事件监听
  • 一个业务域需要拆成独立目录维护
  • 希望业务注册逻辑走统一生命周期,而不是散落在 main.go
  • 希望业务模块之间能声明依赖关系

当前应用自己的业务入口,优先写 Module。可复用框架能力或第三方扩展,才需要写 Provider。

常见错误

把框架能力写成 Module

当前应用自己的业务入口用 Module。可复用的框架能力、驱动和第三方扩展才写 Provider。

Module 里直接创建全局连接

数据库、缓存、队列等通用能力应通过对应 Provider 管理。业务自己的资源如果必须创建,也要在 Shutdown 中释放。

Depends 名称写错

Depends() 返回的是模块 Name(),不是 Go 包名或目录名。

一个实用建议

Module 不一定要很大。一个后台项目可以只有 admin.Module,一个复杂系统可以拆成 user.Moduleorder.Modulepayment.Module。按业务边界拆,不按技术层强行拆。

编辑此页