python构建基于服务的架构(2)
背景
之前的文章python构建基于服务的架构(1), 这种架构有几个小问题:
- 调用服务的方式采用函数调用, 形如: service().sA, 不是很方便. 如果service.sA就更自然一些.
- 不支持类型注解, 比如一个子服务调用另外一个子服务时, 在方法定义中指定类型注解实现起来不方便
- 不支持按需加载子服务. 如果有多个子服务, 会一次性全部加载完, 效率不高. 如果能够按需加载就好了.
今天琢磨出一个新方案, 可以完美解决以上几个问题.
实现方案
建议目录结构
与之前的方案相比, 不需要_services.py
文件了.
my_project/ # 项目目录
├── services/ # 核心服务层
│ ├── services.py # 内部服务和外部用户引用服务的类
│ ├── service_a/ # 服务A(也可调用其他的服务, 比如服务B)
│ │ └── a.py # 服务A的实现
│ ├── service_b/ # 服务B(也可调用其他的服务, 比如服务A)
│ │ └── b.py # 服务B的实现
├── main.py # 用户可以调用服务
服务提供类
一般放在services.py
文件中.
可做如下约定:
- 对于单实例类, 命名使用
s+类名
的方式, 形如:sAbc
- 对于多实例类, 命名使用
m+类名
的方式, 形如:mAbc
- 单实例返回是对象, 多实例返回的是类型. 因为有一些类构造时需要传递参数, 多实例返回类型方便用户构造时传递参数
以下实现代码, 有几个需要注意的点:
- 子服务类型采用
伪类型
, 也就是使用TYPE_CHECKING
. 在实际运行时并不真的引用类型. 只有在使用时才会真的导入类型, 所以类型必须延迟引用, 需要加引号, 形如: "'A'
" - 使用
cached_property
修饰符, 作用是简便的实现单例模式. 避免重复创建对象. 但是python3.8之前没有此修饰符, 可以用property和lru_cache来组合使用, 必须把property放在最外层.
from typing import TYPE_CHECKING
from functools import cached_property
# from functools import lru_cache
if TYPE_CHECKING:
from services.a import A
from services.b import B
class Services:
@cached_property
# @property
# @lru_cache()
def sA(self)-> 'A':
from services.a import A
return A()
@property
def mB(self)-> type['B']:
from services.b import B
return B
service = Services()
调用服务
几个需要注意的点:
- 除了用户可以调用, 服务之间也可以使用同样的代码调用其他服务
- 单例直接使用服务对象, 多例需要调用构造函数
from services.services import *
导入所有, 是为了使用伪类型
from services.services import *
service.sA.get_name()
service.mB().get_name()
关于'伪类型'
其实python提供统一的服务对象, 还是比较简单的. 难点是提供子服务相关的类型, 因为服务之间可能互相调用, 就可能会引起循环引用, 执行时报错. 如果放弃类型信息, 不是很优美, 另外也会影响代码补全等实用功能.
所以, 这个时候就需要'伪类型'技术. 使用TYPE_CHECKING和字符串类型技巧, 享受强类型好处同时又不会造成真引用引起的循环引用问题.