三月沙 原文链接
turbo 与其说是一个 framework ,不如说是一个后端的解决方案。
turbo 是如何诞生的? tornado 是异步非阻塞的 web 服务器,同时也是一个 web framework,功能简单但完全够用。
原东家的技术栈是:tornado、mongodb、redis、openresty,最大的服务每天服务的独立用户有上百万。早期大部分项目完全使用 tornado 最原始的方式构建,一个 main 文件包含几百个路由,所有的 handler 分布在十几个大大小小的文件夹下面,项目基本的文件结构包含:
一个抽象的 dal 层(数据抽象)
数据库连接(db)
配置(conf)
工具(utils)
缓存(cache)
第三方库(lib)
随着项目的代码越来越多,多个特性开发常常同时进行,开发人员经常要非常小心地应对代码的更改,即使如此,冲突依然时常发生,代码的可维护性和健壮性日益变差。
创业公司由于业务面临飞速增长,多条业务线齐头并进,开发团队往往要同时维护很多个项目,有的项目生命周期很短,一次活动就结束了,有的项目要对外合作,随合作的结束而结束,有的项目服务新的产品,需要快速构建。不同的项目由不同的开发人员创建,创建项目的方式基本是 copy 旧项目,比较原始,容易出错。项目结构在不同的项目之间的差异随着业务的不同,差别渐渐凸显,重复的功能性代码和工具性代码也逐渐变多。
在这样的背景之下,影响后端开发效率和开发质量主要问题可归纳为:
大项目路由多,不易管理,不易变更
项目目录分层不够,协同开发困难
构建没有标准,混乱不堪,可维护性差
重复性代码可共享性差,不易插拔
tornado 天生原始,没有 Session、数据库连接、配套的 mongodb ORM、缓存管理、RESTFul等丰富特性辅以提高开发效率。
turbo 就是为解决这些问题而创建的,因而 turbo 更像是一个解决方案。
turbo 具有什么特性?
项目构建工具,一键生成项目目录和 app 结构
Session,提供灵活的接口,可以自定义存储方式
简单的 mongodb ORM,和 mongoose 相比,非常简单甚至粗糙,但足够用了
丰富且高度统一的目录划分,项目再大也不怕
代码可插拔,易移植,易维护
可以方便实现 RESTFul 风格的 api
灵活的路由组织结构,非常方便重构和查找
一键构建 目录结构一键生成,不再是从旧项目拷贝,结构一致,高度统一,可维护性变强。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 % turbo-admin -h turbo init project, server or app. Usage: turbo-admin (-h | --help) turbo-admin startproject <project> turbo-admin startserver <server> turbo-admin startapp <app> turbo-admin index <model_name> Options: -h, --help Show help document % turbo-admin startproject hello-turbo % cd hello-turbo % tree -L 2 ├── app-server │ ├── apps │ ├── main.py │ ├── setting.py │ ├── static │ └── templates ├── config │ └── __init__.py ├── db │ ├── __init__.py │ ├── conn.py │ └── setting.py ├── helpers │ ├── __init__.py │ ├── settings.py │ └── user ├── lib │ ├── __init__.py │ └── session.py ├── models │ ├── __init__.py │ ├── base.py │ ├── settings.py │ └── user ├── service │ └── __init__.py ├── store │ ├── __init__.py │ ├── actions.py │ ├── modules │ └── mutation_types.py └── utils └── __init__.py
Session 自带 Session,存储方式可以自由变更,灵活性强。
1 2 3 from turbo.session import HeaderObject, CookieObjectfrom turbo.session import Store, RedisStore, DiskStore
简单的 mongodb ORM ORM 提供最基本的 collection 结构到代码的映射,表结构清晰明了,增删改均可灵活定义数据一致性检查。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import turbo.modelclass Category (turbo.model.BaseModel) : """分类 field: name: 名称 ename: 英文名 desc: 描述 tag: 标签列表 sn: 序号 nvideo: 视频数量 cover: 封面 uid: 创建用户 atime: 时间 """ name = 'category' field = { 'name' : (basestring, '' ), 'ename' : (basestring, '' ), 'desc' : (basestring, '' ), 'tag' : (list, []), 'sn' : (int, 0 ), 'nvideo' : (int, 0 ), 'cover' : (ObjectId, None ), 'uid' : (ObjectId, None ), 'atime' : (datetime, None ), }
丰富的目录结构划分 严格定义了各个目录的结构的作用以及层次,提供了一般大型项目所需的各种目录划分
1 2 3 4 5 6 7 8 9 10 11 12 ├── README.md ├── app-server ├── config ├── db ├── helpers ├── lib ├── models ├── script ├── service ├── store ├── task ├── utils
代码可插拔, 易移植 1 2 3 4 5 6 7 8 9 apps ├── __init__.py ├── app │ ├── __init__.py │ ├── app.py │ ├── base.py │ ├── setting.py ├── base.py ├── settings.py
每个业务 app 都具有一样的结构,配置和业务代码随着业务所需分布在各个不同的层次,抽象程度越高的代码,所在层次越高,比如全局合法性检查可以放置在 apps/base.py
中,越具体的业务所在的层次越低,比如具体的注册登录逻辑可以放置在 apps/app/app.py
中。如果业务发生变更,业务目录 app
可完整移植。
所有业务 app
都必须在 apps/settings.py
下注册才能生效,上下线 so easy.
RESTFul api http 协议所支持的方法都可以在 turbo 中以大小写的方式区分来实现普通页面 render 和接口 api
1 2 3 4 5 6 7 def get (self) : pass def GET (self) : self._data = {'username' : 'zhyq0826' }
灵活的路由 路由紧随业务代码,turbo 提供了
register_group_urls
register_url
两种方式来实现路由的注册
1 2 3 4 5 6 7 8 register.register_group_urls('' , [ ('/' , app.HomeHandler), ('/index' , app.HomeHandler, 'index' ), ('/hello' , app.HomeHandler, 'home' ), ]) register.register_url('/v1/hello' , app.ApiHandler)
可以十分方便的更改路由规则。
集成很多实用的功能特性 在使用mongodb 和 tornado 的实际开发中遇到了各式各样的问题,因而 turbo 自身在发展过程中集成了很多小功能,非常实用。
1.可以使用命令行一键生成所有 collection 的索引
1 turbo-admin index <model_name>
2.类似 flux 的状态和事件分发机制,可以非常方便管理和更改全局变量状态,此特性可以用以请求统计,函数调用统计等方面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 from turbo.flux import Mutation, register, Statemutation = Mutation(__file__) state = State(__file__) @register(mutation) def increase_rank (rank) : return rank+1 @register(mutation) def dec_rank (rank) : return rank-1 from turbo.flux import Mutation, register, dispatch, register_dispatchimport mutation_types@register_dispatch('user', mutation_types.INCREASE) def increase (rank) : pass def decrease (rank) : return dispatch('user' , mutation_types.DECREASE, rank) @register_dispatch('metric', 'inc_qps') def inc_qps () : pass
3.mongodb 读写调用 hook,利用该 hook ,可以统计 mongodb 读写调用状况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Model (turbo.model.BaseModel) : def write_action_call (self, name, *args, **kwargs) : """ execute when write action occurs, note: in this method write action must be called asynchronously """ pass def read_action_call (self, name, *args, **kwargs) : """ execute when read action occurs, note: in this method read action must be called asynchronously """ pass
4.日志可以根据文件所在路径生成 logger 名称,也可以自定义 logger 路径、级别、名称等1 2 3 4 5 6 from turbo.util import getLoggerlogger = getLogger(__file__) logger = getLogger('feed' , log_level=logging.DEBUG, log_path='/var/log/feed.log' )
5.字符串化 mongodb 数据
1 2 3 4 from turbo.util import to_str, to_list_str, to_dict_str, json_encodedata = json_encode(to_str(db.collection.find()))
6.参数快捷提取和转换
1 2 3 4 5 6 7 8 9 10 _get_params = { 'need' : [ ('skip' , int), ('limit' , int), ], 'option' : [ ('did' , basestring, None ) ] }
可调用 self.parameters 完成参数提取工作,必须的参数不存在或转换错误时,值为 None
,可选的参数可以指定默认值。
1 2 assert self.parameters['skip' ] == 0 assert self.parameters['did' ] == None
除上面列举的之外,turbo 还具有很多实用的特性,仓库地址:https://github.com/wecatch/app-turbo
适时而建的轮子 turbo turbo 在生产环境中应用了近一年,最大的项目日独立用户过百万,还有十多个大大小小的项目的后端使用了 turbo,已经经过了最初的考验。
后端的轮子相比前端来说,远不如后者的来的快、来的猛,后端需要更长久的磨练和雕琢,一点一滴都需要经过业务的锤炼和数据的洗礼,才能渐成佳品,成为解决特定问题的利器。而轮子的创造更要把握时机,顺势而发,过早,轮子不够健全,解决问题不够彻底,很难推广使用,甚至有可能成了绊脚石,过晚,很多问题已经导致代码积劳成疾,想要完全治愈,必然代价不菲。
turbo 正是为了解决 tornado 和 mongodb 类应用在面对代码规模庞大、多人协同所遭遇的种种问题而创造的,它的立足点非常实际,没有炫技,不浮夸,切中要害,直面问题。
turbo 的实用就是它的华丽所在。