# DalPyFrame开发说明
DalPyFrame是`一体化业务开发平台`体系内中后台服务的开发框架。前后台分离,微服务体系,统一中后台结构,支持python和C++语言进行业务单元的开发。框架层级支持自动测试和自动部署和自动运维,说明文档自动生成,使开发同事仅需要聚焦在业务逻辑的实现上。
所有的业务逻辑的实现按照`面向数据编程`的理念进行设计和组织,将中后台服务抽象为若干种业务单元的类型,各类型适用不同的使用场景,同时系统框架接管存储、日志、消息通道、线程、协程、单元间调用、接口权限和数据权限、订阅管理、缓存管理、物理资源管理等公共资源。各业务单元开发重点在于业务逻辑的划分,数据接口和数据结构的设计,业务逻辑的实现。需要注意的是:单元间调用不能成环,业务逻辑实现是无状态的。在高负载情况下,部署框架会自动启动多个业务单元实例进行负载均衡,需要保证在多个实例情况下,业务逻辑可以正确执行,不出现数据一致性问题。
## 系统架构
系统框架如下图所示,对前端提供统一中后台接口,系统框架接管接口调用和数据订阅。目前支持restful、graphql、websocket、rabbitmq、grpc等方式。自动转换到业务单元内到函数接口调用,所使用的消息传递方式对业务代码来讲是无感知的,业务单元开发过程中无需关注。

## 业务单元目录结构及DalPyFrame安装
业务单元有且仅有一个从基础单元类派生的业务单元类,位于工程目录下的main.py中。其他py文件可以同在一级目录,或在子目录中进行组织。要求每个业务单元的开发都要有独立的python虚拟环境,在业务单元工程下的env目录。DalPyFrame采用Wheel安装包形式,不会放到公开的pip仓库,而是采用文件或http链接的形式在公司内部进行发布,使用pip安装。
python虚拟环境的创建命令:
``` bash
[alex.c@devdesktop pu_fakeif]$ virtualenv env
```
建议每个业务单元代码不要过多,复杂业务单元优先考虑按业务逻辑进行拆分,方便开发和维护。大多数业务单元的代码可以在一个main.py中实现,工程目录结构如下:
``` bash
[alex.c@devdesktop pu_fakeif]$ pwd
/home/users/alex.c/temp2/pu_fakeif
[alex.c@devdesktop pu_fakeif]$ ll
total 4
drwxr-xr-x 5 alex.c admin 77 Feb 16 11:25 env
-rw-r--r-- 1 alex.c admin 1763 Feb 16 11:25 main.py
```
进入python虚拟环境中:
``` bash
[alex.c@devdesktop pu_fakeif]$ source env/bin/activate
```
使用文件或链接安装DalPyFrame安装包,同时设置pip指向国内镜像,以下载依赖包:
```bash
(env) [alex.c@devdesktop pu_fakeif]$ pip install https://store.daline.com.cn/glb/DalPyFrame-1.0.1-py36-none-any.whl -i https://pypi.douban.com/simple
```
安装完成后,可以在python虚拟环境的`lib/python3.6/site-packages/`下看到dalpyframe目录结构:
```bash
(env) [alex.c@devdesktop pu_fakeif]$ tree env/lib/python3.6/site-packages/dalpyframe/
env/lib/python3.6/site-packages/dalpyframe/
├── cron_regexes.pyc
├── cron_util.pyc
├── cron_validator.pyc
├── doc
│ ├── readme.md
│ └── res
│ └── frame.png
├── examples
│ ├── pu_db
│ │ ├── main.py
│ │ └── __pycache__
│ │ └── main.cpython-36.pyc
│ ├── pu_fakeif
│ │ ├── main.py
│ │ └── __pycache__
│ │ └── main.cpython-36.pyc
│ ├── pu_fakeifthread
│ │ ├── main.py
│ │ └── __pycache__
│ │ └── main.cpython-36.pyc
│ ├── pu_log
│ │ ├── main.py
│ │ └── __pycache__
│ │ └── main.cpython-36.pyc
│ └── pu_store
│ ├── main.py
│ └── __pycache__
│ └── main.cpython-36.pyc
├── frame.pyc
├── __init__.pyc
└── unit.pyc
13 directories, 18 files
```
doc目录下为markdown格式说明文档,example目录下为各类业务单元和各类资源使用示例。
## 业务单元类型
目前业务单元一共3种类型:
PU(process unit): 为常驻型中后台服务,启动后持续运行。系统自动运维,故障自动重启,实例负载过重时框架自动启动多实例进行负载均衡;
TU(task unit): 定时任务单元,用于执行数据处理任务,使用cron格式描述启动时间;
TSU(trade strategy unit): 交易策略单元,提供策略回测和执行框架;
## 公共资源
### 日志
框架提供统一日志管理,各类业务单元中使用`self.logger`进行日志操作,例:
```python
self.logger.debug("This is a debug log!")
self.logger.info("This is a info log!")
self.logger.warning("This is a warning log!")
self.logger.error("This is a error log!")
try:
k = 32/0
except Exception as exp:
self.logger.exception("This is a exception log!")
```
exception会自动打印错误堆栈。可参见示例`examples/pu_log`
### 文件存储
框架提供临时存储,nfs存储,http存储,oss存储四种类型的文件存储。
1. 临时存储:使用宿主机本地存储,读写性能较高,仅本单元本实例可见,单元托管运行后故障重启或宿主机漂移等情况下,数据会丢失,仅做本地缓存使用;
2. nfs存储:使用数据中心网络存储,读写性能稍弱,有备份机制,数据永久保存。本作者名下所有单元和单元所有实例均可以见。可用于数据永久存储和本作者名下所有单元数据交互;
3. http存储:使用数据中心网络存储,并通过http协议提供外部访问,用于跨作者数据访问或共享数据文件给外部访问;
4. oss存储:使用阿里云oss存储,读写性能最弱,不建议新增单元使用,现有单元使用位置逐渐使用http存储替代。
文件存储使用示例:
单元启动时,临时存储路径设在`self.folder_temp`,nfs存储路径设在`self.folder_nfs`,http存储路径设在`self.folder_http`,这三种存储中文件和目录操作方式与本地文件和目录操作方式相同。http存储中的目录和文件可以通过调用`self.http_link`获取外部访问链接。
oss存储通过内置`self.oss_*`系列函数操作,需要通过上传和下载动作操作文件。
```python
with open(self.folder_temp+'try_write.txt','w',encoding='utf8') as f:
f.write('hello temp folder')
with open(self.folder_nfs+'try_write.txt','w',encoding='utf8') as f:
f.write('hello nfs folder')
with open(self.folder_http+'try_write.txt','w',encoding='utf8') as f:
f.write('hello http folder')
self.logger.info("visit http file from %s",
self.http_link(self.folder_http+'try_write.txt'))
self.oss_upload_file('try_write.txt',self.folder_temp+'try_write.txt')
self.oss_set_acl('try_write.txt',2)
self.logger.info("visit oss file from %s",
self.oss_get_file_info('try_write.txt').resp.response.url)
```
存储的使用可参见示例`examples/pu_db`
### 数据库存储
目前仅支持mysql数据库,后期会根据需要陆续扩展。
数据库的操作借助sqlalchemy库,推荐使用ORM方式进行数据库操作,ORM无法支持的操作才使用Core方式。sqlalchemy使用说明参考:
数据库的连接框架已经封装,所有ORM方式的数据库类继承自`dalpyframe.frame.Base`,会自动创建数据库。数据库操作通过`self.sql_session`。
使用示例参见`examples/pu_db`
## PU结构说明
### 类声明
PU为常驻任务,每个PU单元在main.py中定义且仅定义一个派生自`dalpyframe.unit.ProcessUnit`的类。并在此类的docstring中的`Attributes`项中包含`CPU`和`Memory`的描述,均为整数,`CPU`以核数为单位, `Memory`以`MB`为单位。例如:
```python
class FakeIF(ProcessUnit):
"""
常驻任务单元,需描述最小资源要求
Attributes:
CPU: 1
Memory: 32
"""
```
### 自定义初始化函数
每个PU单元必须重载`custom_init`方法,返回True或False。在单元启动时框架会自动调用此函数进行自定义初始化,如返回False或抛出异常,框架会持续再调用,知道自定义初始化完成并返回True。初始化结束前,所有接口调用返回正在初始化中的错误。示例:
```python
def custom_init(self):
"""
业务单元自定义初始化
"""
return True
```
### 对外接口
以`面向数据编程`的理念进行接口设计,PU单元只有一级接口,所有接口函数以`***_Data`方式命名,框架会自动识别此接口。所有接口入参为`caller`和`index`,`caller`包含调用者信息,用于业务单元内部进行数据权限控制,`index`包含所有入参,以字典形式存储。
接口需要编写docstring,必须有`Attributes`项包含`ACCode`字段,用于描述接口权限,采用与Linu文件权限相同的表示方式`111`,本人、本组和其他组。接口仅有调用权限。必须有`Returns`项,用于描述返回的自定义数据结构,此描述同时用于文档生成和graphql接口支持。必须有`Examples`项,提供可以使用的调用示例。
接口返回数据格式固定为`(ret_code,ret_info,ret_data)`,`ret_code`为执行结果码,0表示执行成功,非0为失败,1~100为框架预留,自定义结果码100以上;`ret_info`为结果描述,建议优先使用英文描述;`ret_data`为结果数据,自定义数据格式。
示例如下:
```python
def Example_Data(self,caller,index):
"""
获取模拟客户信息
Attributes:
ACCode: 111
Returns:
name (str): 客户名
ssn (str): 身份证号码
mobile (str): 手机号码
company (str): 公司名
email (str): 电子邮箱
address (str): 地址
Examples:
{}
"""
return (0,'success',self._fake_data)
```
### 数据订阅
客户端可以在中后台订阅数据,数据变化时,框架自动推送新数据到终端。数据订阅以接口为单位。推送的数据格式为`[(fidx,type,index,timestamp,data),]`,更新数据以数组形式推送。`fidx`为接口名,`type`为变更类型,只能是`new`,`update`,`del`之一,`index`为自定义数据索引,`timestamp`为数据时间戳用于最新数据判断,`data`为最新数据,如为`del`操作,`data`项设为空。
### 数据更新协程
协程函数用于业务单元内部数据更新及维护,执行频率低于等于秒级,协程函数命名为`***_Cron`,框架会自动识别,并按描述要求定时调用。
接口需要编写docstring,必须有`Attributes`项包含`cron`字段,前五位与Linux系统中cron格式相同,多增的第六位为秒位,用法与分钟位相同,格式说明参考,或者在linux下直接`man 5 crontab`查看。必须有`Returns`项,指明更新哪些接口的数据。数据更新后,框架会自动进行订阅管理并推送更新数据给到所有订阅此接口的终端。
返回格式为字典,`fidx:[(type,index,timestamp,data),]`。
示例如下:
```python
def maintain_Cron(self):
"""
秒级及以上定时任务,用于维护数据,线程数量可以根据业务逻辑自由安排,可以一个单元一个,也可以一个接口一个,使用***_Cron声明即可;
频率高于秒级使用***_Thread;
返回格式为{接口名:[(变更类型,索引,数据变更时间戳,最新数据),...],...}
此示例中接口名为Example
变更类型为new del update 之一
索引为数据在接口中的索引值,根据业务逻辑自定义,所有数据要求有索引,单值数据索引可以为空
最新数据为变更后的最新数据,如为del操作,最新数据为空
Attributes:
cron: * * * * * */2
Returns:
Example1 Example2
"""
ret_data = {}
ret_data['Example1'] = []
self._fake_data1['name'] = self._faker.name()
self._fake_data1['ssn'] = self._faker.ssn()
self._fake_data1['mobile'] = self._faker.phone_number()
self._fake_data1['company'] = self._faker.company()
self._fake_data1['email'] = self._faker.email()
self._fake_data1['address'] = self._faker.address()
self._date_time1 = time.localtime()
ret_data['Example1'].append(('update',{},self._date_time1,self._fake_data1))
return ret_data
```
### 数据更新线程
对于更新频率为毫秒级或常驻更新任务,采用线程函数处理。函数声明为`***_Thread`,框架会自动识别并启动线程。线程函数的docstring不需要`Attributes`项,其他要求及函数返回格式要求与协程函数一致。
示例如下:
```python
def maintain_Thread(self):
"""
常驻线程,用于维护数据,线程数量可以根据业务逻辑自由安排,可以一个单元一个,也可以一个接口一个,使用***_Thread声明即可;
刷新频率较低的数据维护任务使用***_Cron
返回格式为[(接口名,变更类型,索引,数据变更时间戳,最新数据),...]
此示例中接口名为Example
变更类型为new del update 之一
索引为数据在接口中的索引值,根据业务逻辑自定义,所有数据要求有索引,单值数据索引可以为空
最新数据为变更后的最新数据,如为del操作,最新数据为空
Returns:
Example2
"""
ret_data = {}
ret_data['Example2'] = []
self._fake_data2['name'] = self._faker.name()
self._fake_data2['ssn'] = self._faker.ssn()
self._fake_data2['mobile'] = self._faker.phone_number()
self._fake_data2['company'] = self._faker.company()
self._fake_data2['email'] = self._faker.email()
self._fake_data2['address'] = self._faker.address()
self._date_time2 = time.localtime()
ret_data['Example2'].append(('update',{},self._date_time1,self._fake_data1))
return ret_data
```
### 单元测试函数
每个PU单元必须重载并实现`test`函数作为单元测试函数,框架会使用此函数进行单元测试,记录并汇总结果。单元测试涵盖范围尽量完整。
示例如下:
```python
def test(self):
"""
单元测试
"""
self.Example_Data({},{})
return True
```
## TU结构说明
开发中...
## TSU结构说明
开发中...
## 开发环境
### 插件使用
所有开发使用vscode,集中远程开发方式。开发机为devdesktop,centos7环境,python版本固定为3.6.8。vscode开发python可以参考
vscode推荐插件如下
本地终端:
1. Remote-SSH: 远程登入devdesktop
远程开发机(devdesktop):
1. Git History:git插件
2. GitLens:git插件
3. Markdown All in One:markdown文档查看器
4. Pylance:Python语法提示
5. Vetur:Vue开发插件
### 语法提示
python开发要求开启编码规则检查,在工程的`.vscode`目录下填加`settings.json`文件,使用pylint检查规则,内容如下:
```json
{
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.linting.flake8Enabled": false,
"python.defaultInterpreterPath": "/home/users/alex.c/workdir/DalPyFrame/env/bin/python"
}
```
`python.defaultInterpreterPath`指向工程所使用的python虚拟环境,该环境中需要安装pylint。
### 调试配置
在vscode中`F5`键可以直接启动调试,在工程的`.vscode`目录下设置`launch.json`文件,参考如下:
```json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "/home/users/alex.c/workdir/DalPyFrame/examples/pu_store/main.py",
"console": "integratedTerminal",
"python": "/home/users/alex.c/workdir/DalPyFrame/env/bin/python", //python虚拟环境
"env": {
"LOG_LEVEL":"debug","LOG_DIR":"/home/users/alex.c/temp/log/daline",
"PORT":"12080","SOCKET_PORT":"12081",
"MYSQL_SERVER":"172.16.0.10","MYSQL_PORT":"3306","MYSQL_ACCOUNT":"sqltest","MYSQL_PASSWORD":"Hell0Mysql","MYSQL_DB":"sqltest",
"TEMP_FOLDER":"/home/users/alex.c/temp3/tmp/","NFS_FOLDER":"/home/users/alex.c/temp3/nfs/","HTTP_FOLDER":"/home/users/alex.c/temp3/http/","HTTP_BASE_URL":"http://store.daline.com.cn/dev/",
"OSS_KEY_ID":"LTAI4GCs6bsirTckr9B6tNue",
"OSS_KEY_SECRET":"HSOyM6ZxYTPF2EXMKbn2MuSkz0Uooa",
"OSS_END_POINT":"oss-cn-shanghai.aliyuncs.com",
"OSS_BUCKET_NAME":"daloss",
"OSS_FOLDER":"dev"
},
"cwd": "/home/users/alex.c/workdir/DalPyFrame"
}
]
}
```
业务单元所使用资源可用通过环境变量设置,也可以通过在frame中输入作者账户和密码自动获取系统预设资源配置。环境变量优先级高于通过账户获取系统预设值。业务单元提交托管后由系统自动设置相应资源的配置值。
1. LOG_LEVEL:日志级别,'debug','info','warning','error'
2. LOG_DIR: 日志存储路径
3. PORT: restful接口的端口号
4. SOCKET_PORT: websoket订阅接口的端口号
5. MYSQL_XXX: mysql数据库配置
6. ***_FOLDER: 各存储类型的目录
7. HTTP_BASE_URL: 外部访问共享文件的URL链接前缀
8. OSS_***: 阿里云oss相关参数
### 本地调试
devdesktop开发机的ip地址为172.16.0.59,业务单元启动后,可以通过postman进行端口调用调试,如下图所示:

postman同时支持websocket调试,新建websocket页面:

通过`subscribe`事件进行订阅,订阅输入为`{"fidx":"Example1"}`,`Example1`为接口名:

可以设置监听事件,事件名为接口名,新数据会推送并显示在订阅的事件下:

postman使用常见问题:
postman对https证书的检测规则与浏览器不一样,对我们使用的证书存在误报证书过期的问题,即https调用失败,并在`console`中显示返`"Error: certificate has expired"`,处理方法为在postman的设置中关闭SSL证书检测,如下图所示:

### 存储查看
目前nfs存储和http存储使用同一个NFS根存储。已经挂在到devdesktop上,各环境业务单元托管时通过挂载不同目录进行控制。因为nfs存储根目录已经关载到devdesktop,所以各环境,所有单元所使用的存储都可以看到。挂载根目录为`/NFSData`。
1. 开发环境nfs存储跟路径为`/NFSData/DevRes`
2. 测试环境nfs存储跟路径为`/NFSData/TestRes`
3. 生产环境nfs存储跟路径为`/NFSData/ProdRes`
4. 开发环境http存储跟路径为`/NFSData/FileServer/files/dev`
5. 测试环境http存储跟路径为`/NFSData/FileServer/files/test`
6. 生产环境http存储跟路径为`/NFSData/FileServer/files/prod`
OSS查看需登入到阿里云账户。
### 用户信息查看
各环境用户和组均使用ldap存储和管理,所有环境单独部署,数据不影响,可以通过openldap查看。
### 数据库查看
各环境mysql数据单独部署,数据不影响,可以通过phpmyadmin登录查看。
### 数据通道
目前仅支持restful接口和websocket数据推送。其他数据通道逐渐完善,对业务单元来说,对所用消息通道无感知,均由框架自动处理。
## 编码规范要求
python编码规范要求准守,代码要求vscode开启编码规则检测,pylint规则不报错。在代码提交时会再进行一次pylint检测,由报错时禁止代码提交。
## 示例说明
目前提供了几个基础示例,演示主要功能用法,可以参考,示例位于`env/lib/python3.6/site-packages/dalpyframe/examples`下:
```bash
[alex.c@devdesktop DalPyFrame]$ tree examples/
examples/
├── pu_db
│ └── main.py
├── pu_fakeif
│ └── main.py
├── pu_fakeifthread
│ └── main.py
├── pu_log
│ └── main.py
└── pu_store
└── main.py
5 directories, 5 files
```
1. pu_db: 数据库ORM方式用法
2. pu_fakeif: 基础接口调用
3. pu_fakeifthread: 提供协程和线程方式更新数据,支持数据订阅
4. pu_log: 日志使用演示
5. pu_store: 存储使用演示