paopao - 关于功能项配置解析的设计

paopao-ce 是使用Golang技术栈打造的一个清新文艺的微社区,编译后的可执行文件外加一个配置文件conf.yml就可以简单部署一个节点实例提供类似Twitter的推文服务。

paopao-ce的配置文件中有一个功能项的配置用于自定义节点实例运行时提供哪些服务,对部署运维非常友好。本文简单解析conf.yml中功能项配置的设计,了解其背后的实现细节。

conf.yml的功能项配置机制是参考Rust包管理工具Cargo的 Cargo.toml[features] 配置而设计的。如下示例:

[package]
name = "poem"
version = "1.3.50"
authors = ["sunli <scott_s829@163.com>"]
edition = "2021"
description = "Poem is a full-featured and easy-to-use web framework with the Rust programming language."

[features]
default = ["server"]
server = ["tokio/rt", "tokio/net", "hyper/server", "hyper/runtime"]
websocket = ["tokio/rt", "tokio-tungstenite", "base64"]
multipart = ["multer"]
rustls = ["server", "tokio-rustls", "rustls-pemfile"]
native-tls = ["server", "tokio-native-tls"]
openssl-tls = ["server", "tokio-openssl", "openssl"]
static-files = ["httpdate", "mime_guess", "tokio/io-util", "tokio/fs"]
cookie = ["libcookie", "chrono", "time"]
session = ["tokio/rt", "cookie", "rand", "priority-queue", "base64"]
embed = ["rust-embed", "hex", "mime_guess"]
xml = ["quick-xml"]
i18n = [
    "fluent",
    "fluent-langneg",
    "fluent-syntax",
    "unic-langid",
    "intl-memoizer",
]

在Rust中,使用features的方式

pub mod endpoint;
pub mod error;
#[cfg(feature = "i18n")]
#[cfg_attr(docsrs, doc(cfg(feature = "i18n")))]
pub mod i18n;
#[cfg(feature = "server")]
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
pub mod listener;
pub mod middleware;
#[cfg(feature = "session")]
#[cfg_attr(docsrs, doc(cfg(feature = "session")))]
pub mod session;
#[cfg(feature = "test")]
#[cfg_attr(docsrs, doc(cfg(feature = "test")))]
pub mod test;
pub mod web;

#[doc(inline)]
pub use http;

没错,很简单,只要在需要feature时使用#[cfg(feature = "some-feature")]注解即可,Rust编译器会做相应处理。

paopao-ce使用YAML格式的配置文件conf.yml,在[Features]小节中声明运行时开启哪些功能项/服务:

...
Features:
  Default: ["Base", "MySQL", "Option", "LocalOSS", "LoggerFile"]
  Develop: ["Base", "MySQL", "Option", "Sms", "AliOSS", "LoggerZinc"]
  Demo: ["Base", "MySQL", "Option", "Sms", "MinIO", "LoggerZinc"]
  Slim: ["Base", "Sqlite3", "LocalOSS", "LoggerFile"]
  Base: ["Zinc", "Redis", "Alipay"]
  Docs: ["Docs:OpenAPI"]
  Service: ["Admin", "SpaceX", "Bot", "LocalOSS"]
  Deprecated: ["Deprecated:OldWeb"]
  Option: ["SimpleCacheIndex"]
  Sms: "SmsJuhe"
...

如上: Default/Develop/Demo/Slim 是不同 功能集套件(Features Suite), Base/Option 是子功能套件, Sms是关于短信验证码功能的参数选项。

这里 Default套件 代表的意思是: 使用Base/Option 中的功能,外加 MySQL/LocalOSS/LoggerFile功能,也就是说开启了Zinc/Redis/Alipay/SimpleCacheIndex/MySQL/LocalOSS/LoggerFile 7项功能; Develop套件依例类推。

使用Features:

release/paopao-ce --help
Usage of release/paopao-ce:
  -features value
        use special features
  -no-default-features
        whether use default features

# 默认使用 Default 功能套件
release/paopao-ce 

# 不包含 default 中的功能集,仅仅使用 develop 中声明的功能集
release/paopao-ce --no-default-features --features develop 

# 使用 default 中的功能集,外加 sms 功能
release/paopao-ce --features sms  

# 手动指定需要开启的功能集
release/paopao-ce --no-default-features --features sqlite3,localoss,loggerfile,redis 

paopao-ce在代码实现中是通过github.com/alimy/cfg解析Features配置:

初始化cfg

// file: internal/conf/conf.go

package conf

import (
	"log"
	"sync"
	"time"

	"github.com/alimy/cfg"
)

func setupSetting(suite []string, noDefault bool) error {
  ...
  
	// initialize features configure
	ss, kv := setting.featuresInfoFrom("Features")
	cfg.Initial(ss, kv)
	if len(suite) > 0 {
		cfg.Use(suite, noDefault)
	}
  
  ...
}

cfg的参考使用

// file: internal/service/service.go

package service

import (
	"log"

	"github.com/alimy/cfg"
	"github.com/rocboss/paopao-ce/pkg/types"
)

func newService() (ss []Service) {
	ss = append(ss, newWebService())

	// add oldWebService if not depredcated OldWebService
	cfg.Not("Deprecated:OldWeb", func() {
		ss = append(ss, newOldWebService())
	})

	cfg.In(cfg.Actions{
		"Admin": func() {
			ss = append(ss, newAdminService())
		},
		"SpaceX": func() {
			ss = append(ss, newSpaceXService())
		},
		"Bot": func() {
			ss = append(ss, newBotService())
		},
		"LocalOSS": func() {
			ss = append(ss, newLocalossService())
		},
	})

	return
}
// file: internal/dao/dao.go 

package dao

import (
	"sync"

	"github.com/alimy/cfg"
	"github.com/rocboss/paopao-ce/internal/core"
	"github.com/rocboss/paopao-ce/internal/dao/jinzhu"
	"github.com/rocboss/paopao-ce/internal/dao/sakila"
	"github.com/rocboss/paopao-ce/internal/dao/search"
	"github.com/rocboss/paopao-ce/internal/dao/slonik"
	"github.com/rocboss/paopao-ce/internal/dao/storage"
	"github.com/sirupsen/logrus"
)

func newAuthorizationManageService() (s core.AuthorizationManageService) {
	if cfg.If("Gorm") {
		s = jinzhu.NewAuthorizationManageService()
	} else if cfg.If("Sqlx") && cfg.If("MySQL") {
		s = sakila.NewAuthorizationManageService()
	} else if cfg.If("Sqlx") && (cfg.If("Postgres") || cfg.If("PostgreSQL")) {
		s = slonik.NewAuthorizationManageService()
	} else {
		s = jinzhu.NewAuthorizationManageService()
	}
	return
}

github.com/alimy/cfg的API文档请参考 GoDoc

 MySQL和PostgreSQL的常用语法差异 paopao - 关于paopao-ce的设计定位 

Comments