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和字符串类型技巧, 享受强类型好处同时又不会造成真引用引起的循环引用问题.

posted @ 2025-04-24 17:16  顺其自然,道法自然  阅读(20)  评论(0)    收藏  举报