manager.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. """数据库门面管理器 —— 依赖反转,接收已创建的 Backend 实例
  2. 与旧版 DatabaseManager 的核心区别:
  3. - 不再通过 config dict 内部创建后端,而是接收 Dict[str, BaseBackend]
  4. - 后端创建逻辑剥离为 create_backend() 工厂函数,由调用方(DI 容器 / app 工厂)负责
  5. - Manager 只关心生命周期编排与统一访问路由
  6. """
  7. import logging
  8. from typing import Any, Dict, List, Optional
  9. from supply.infra.database.ports import (
  10. BackendType,
  11. BaseBackend,
  12. SqlBackend,
  13. )
  14. from supply.infra.database.mysql import MySQLBackend
  15. from supply.infra.database.postgresql import PgBackend
  16. from supply.infra.database.milvus import MilvusBackend
  17. from supply.infra.database.redis import RedisBackend
  18. logger = logging.getLogger(__name__)
  19. # ==================== 后端工厂函数 ====================
  20. def _detect_backend_type(config: Any) -> BackendType:
  21. """从配置推断后端类型:优先读取显式 backend 字段,否则按类名匹配"""
  22. if hasattr(config, "backend") and config.backend:
  23. try:
  24. return BackendType(config.backend)
  25. except ValueError:
  26. pass
  27. type_name = type(config).__name__.lower()
  28. for bt in BackendType:
  29. if bt.value in type_name:
  30. return bt
  31. return BackendType.MYSQL
  32. def create_backend(name: str, config: Any) -> BaseBackend:
  33. """工厂函数:根据配置创建对应后端实例
  34. 由调用方(app 工厂 / DI 容器)使用,创建实例后传给 DatabaseManager。
  35. """
  36. bt = _detect_backend_type(config)
  37. if bt == BackendType.MYSQL:
  38. return MySQLBackend(name, config)
  39. elif bt == BackendType.POSTGRESQL:
  40. return PgBackend(name, config)
  41. elif bt == BackendType.MILVUS:
  42. return MilvusBackend(name, config)
  43. elif bt == BackendType.REDIS:
  44. return RedisBackend(name, config)
  45. raise ValueError(f"未知的存储后端类型: {bt}")
  46. # ==================== 门面管理器 ====================
  47. class DatabaseManager:
  48. """多后端数据库门面
  49. 接收已创建的后端实例,负责:
  50. - 统一生命周期管理 (init_pools / close_pools)
  51. - 向后兼容 SQL 接口
  52. - 类型安全的后端直取访问器
  53. 使用方式:
  54. backends = {
  55. "aigc": MySQLBackend("aigc", AigcDatabaseConfig()),
  56. }
  57. db = DatabaseManager(backends)
  58. await db.init_pools()
  59. rows = await db.async_fetch("SELECT * FROM t")
  60. """
  61. def __init__(
  62. self,
  63. backends: Dict[str, BaseBackend],
  64. *,
  65. default_db: str = "default",
  66. ):
  67. if not backends:
  68. raise ValueError("DatabaseManager 至少需要一个后端")
  69. self._backends = backends
  70. self._default_db = default_db
  71. # ==================== 生命周期 ====================
  72. async def init_pools(self) -> None:
  73. for name, backend in self._backends.items():
  74. await backend.init()
  75. logger.info("存储后端 [%s] 初始化完成 → %s", name, type(backend).__name__)
  76. async def close_pools(self) -> None:
  77. for name, backend in self._backends.items():
  78. await backend.close()
  79. logger.info("存储后端 [%s] 已关闭", name)
  80. # ==================== 向后兼容 SQL 接口 ====================
  81. async def async_fetch(
  82. self,
  83. query: str,
  84. *,
  85. params: Optional[tuple] = None,
  86. db_name: Optional[str] = None,
  87. ) -> List[Dict[str, Any]]:
  88. backend = self._get_sql(db_name)
  89. return await backend.fetch(query, params=params)
  90. async def async_fetch_one(
  91. self,
  92. query: str,
  93. *,
  94. params: Optional[tuple] = None,
  95. db_name: Optional[str] = None,
  96. ) -> Optional[Dict[str, Any]]:
  97. backend = self._get_sql(db_name)
  98. return await backend.fetch_one(query, params=params)
  99. async def async_save(
  100. self,
  101. query: str,
  102. *,
  103. params: Any = None,
  104. batch: bool = False,
  105. db_name: Optional[str] = None,
  106. ) -> int:
  107. backend = self._get_sql(db_name)
  108. return await backend.execute(query, params=params, batch=batch)
  109. # ==================== 类型安全的后端直取 ====================
  110. def mysql(self, name: Optional[str] = None) -> MySQLBackend:
  111. return self._get_typed(name or self._default_db, MySQLBackend)
  112. def pg(self, name: str) -> PgBackend:
  113. return self._get_typed(name, PgBackend)
  114. def milvus(self, name: str) -> MilvusBackend:
  115. return self._get_typed(name, MilvusBackend)
  116. def redis(self, name: str) -> RedisBackend:
  117. return self._get_typed(name, RedisBackend)
  118. # ==================== 辅助 ====================
  119. def get_pool(self, db_name: str):
  120. """向后兼容:返回 MySQL 连接池对象"""
  121. backend = self.mysql(db_name)
  122. return backend._pool
  123. def list_databases(self) -> List[str]:
  124. return list(self._backends.keys())
  125. def list_backends(self) -> Dict[str, str]:
  126. return {n: type(b).__name__ for n, b in self._backends.items()}
  127. # ==================== 内部 ====================
  128. def _get_backend(self, db_name: Optional[str] = None) -> BaseBackend:
  129. name = db_name or self._default_db
  130. backend = self._backends.get(name)
  131. if backend is None:
  132. raise KeyError(f"存储后端 '{name}' 未注册,可用: {self.list_databases()}")
  133. return backend
  134. def _get_sql(self, db_name: Optional[str] = None) -> SqlBackend:
  135. backend = self._get_backend(db_name)
  136. if not isinstance(backend, SqlBackend):
  137. raise TypeError(
  138. f"后端 '{db_name}' 类型为 {type(backend).__name__},不支持 SQL 操作"
  139. )
  140. return backend
  141. def _get_typed(self, name: str, expected_type: type) -> BaseBackend:
  142. backend = self._get_backend(name)
  143. if not isinstance(backend, expected_type):
  144. raise TypeError(
  145. f"后端 '{name}' 实际类型为 {type(backend).__name__},"
  146. f"期望 {expected_type.__name__}"
  147. )
  148. return backend