refactor(startup): move domain assembly to composition

This commit is contained in:
jxxghp
2026-08-30 11:52:59 +08:00
parent 7f5b8b469a
commit 2a7f8551b1
4 changed files with 89 additions and 40 deletions

View File

@@ -0,0 +1,43 @@
"""领域规则、解析运行时与技术端口的宿主组合 owner。"""
from app.adapters.network.resolver import SocketDnsResolver
from app.adapters.system import rust as rust_accelerator
from app.adapters.system.host import SystemUtils
from app.application.directory import configure_disk_topology
from app.application.recognition import RecognitionRuleService
from app.application.rules import configure_filter_rule_parser
from app.application.security.url import configure_dns_resolver
from app.application.transfer.workflow import configure_directory_size
from app.domain.media import configure_search_source_provider
from app.domain.meta.customization import configure_customization_provider
from app.domain.meta.releasegroup import configure_release_groups_provider
from app.domain.meta.runtime import configure_recognition_runtime
from app.domain.meta.words import configure_custom_words_provider
from app.domain.metainfo import clear_rust_parse_options_cache
from app.runtime.settings import get_runtime_setting
def compose_domain_dependencies() -> None:
"""构造并发布领域模型使用的规则、配置和技术 Adapter。"""
from app.domain.projection.tmdb import configure_image_url_builder
rule_service = RecognitionRuleService()
configure_dns_resolver(SocketDnsResolver())
configure_disk_topology(SystemUtils)
configure_filter_rule_parser(rust_accelerator)
configure_directory_size(SystemUtils)
configure_customization_provider(rule_service.get_customization)
configure_release_groups_provider(rule_service.get_release_groups)
configure_custom_words_provider(rule_service.get_custom_words)
configure_search_source_provider(lambda: get_runtime_setting("SEARCH_SOURCE"))
configure_image_url_builder(get_runtime_setting("TMDB_IMAGE_URL"))
configure_recognition_runtime(
media_extensions_provider=lambda: (
*get_runtime_setting("RMT_MEDIAEXT"),
*get_runtime_setting("RMT_SUBEXT"),
*get_runtime_setting("RMT_AUDIOEXT"),
),
audio_extensions_provider=lambda: get_runtime_setting("RMT_AUDIOEXT"),
accelerator=rust_accelerator,
)
clear_rust_parse_options_cache()

View File

@@ -1,41 +1,8 @@
from app.adapters.network.resolver import SocketDnsResolver
from app.adapters.system import rust as rust_accelerator
from app.adapters.system.host import SystemUtils
from app.application.directory import configure_disk_topology
from app.application.recognition import RecognitionRuleService
from app.application.rules import configure_filter_rule_parser
from app.application.security.url import configure_dns_resolver
from app.application.transfer.workflow import configure_directory_size
from app.domain.media import configure_search_source_provider
from app.domain.meta.customization import configure_customization_provider
from app.domain.meta.releasegroup import configure_release_groups_provider
from app.domain.meta.runtime import configure_recognition_runtime
from app.domain.meta.words import configure_custom_words_provider
from app.domain.metainfo import clear_rust_parse_options_cache
from app.runtime.settings import get_runtime_setting
"""领域依赖生命周期入口。"""
from app.startup.composition.domain import compose_domain_dependencies
def configure_domain_dependencies() -> None:
"""在组合根集中注入领域模型需要的配置、持久化规则和加速适配器。"""
from app.domain.projection.tmdb import configure_image_url_builder
rule_service = RecognitionRuleService()
configure_dns_resolver(SocketDnsResolver())
configure_disk_topology(SystemUtils)
configure_filter_rule_parser(rust_accelerator)
configure_directory_size(SystemUtils)
configure_customization_provider(rule_service.get_customization)
configure_release_groups_provider(rule_service.get_release_groups)
configure_custom_words_provider(rule_service.get_custom_words)
configure_search_source_provider(lambda: get_runtime_setting('SEARCH_SOURCE'))
configure_image_url_builder(get_runtime_setting('TMDB_IMAGE_URL'))
configure_recognition_runtime(
media_extensions_provider=lambda: (
*get_runtime_setting('RMT_MEDIAEXT'),
*get_runtime_setting('RMT_SUBEXT'),
*get_runtime_setting('RMT_AUDIOEXT'),
),
audio_extensions_provider=lambda: get_runtime_setting('RMT_AUDIOEXT'),
accelerator=rust_accelerator,
)
clear_rust_parse_options_cache()
"""按生命周期顺序调用领域组合 owner。"""
compose_domain_dependencies()

View File

@@ -157,8 +157,12 @@ access provider assembly; it returns the persistence factories needed by the run
owner, which alone constructs `AuthenticationRuntime`.
`startup/composition/network.py` owns concrete network-test, image, internal-address
and message-ingress Adapter wiring
while retaining lazy RuntimeSettings reads. Initializers call these owners in
startup order and must not recreate their concrete construction.
while retaining lazy RuntimeSettings reads. Initializers call these owners in
startup order and must not recreate their concrete construction.
`startup/composition/domain.py` owns DNS/System/Rust Adapter assembly together
with the single `RecognitionRuleService` used to publish media-recognition rule
providers. `initializers/domain.py` is only the lifecycle hook and must not
construct a parallel rule service or import concrete adapters.
`startup/composition/outbox.py`
owns durable handler validation, lazy event/notification replay binding and the
short-transaction dispatcher factory. Initializers call this owner in

View File

@@ -2428,6 +2428,41 @@ def test_plugin_market_transport_is_process_shared_across_compat_and_composition
assert PluginMarketTransport.get_existing_instance() is _plugin_market_transport
def test_domain_initializer_delegates_construction_to_composition():
"""领域 initializer 只负责生命周期调用,不得直接构造 Adapter 或领域服务。"""
initializer_path = APP_ROOT / "startup" / "initializers" / "domain.py"
initializer_tree = ast.parse(
initializer_path.read_text(encoding="utf-8-sig"),
filename=str(initializer_path),
)
imported_modules = {
node.module
for node in ast.walk(initializer_tree)
if isinstance(node, ast.ImportFrom) and node.module
}
assert imported_modules == {"app.startup.composition.domain"}
calls = [
ast.unparse(node.func)
for node in ast.walk(initializer_tree)
if isinstance(node, ast.Call)
]
assert calls == ["compose_domain_dependencies"]
composition_path = APP_ROOT / "startup" / "composition" / "domain.py"
composition_tree = ast.parse(
composition_path.read_text(encoding="utf-8-sig"),
filename=str(composition_path),
)
composition_imports = {
node.module
for node in ast.walk(composition_tree)
if isinstance(node, ast.ImportFrom) and node.module
}
assert "app.adapters.network.resolver" in composition_imports
assert "app.adapters.system.host" in composition_imports
assert "app.application.recognition" in composition_imports
PROCESS_LEVEL_ROOTS = (
"app.api",
"app.chain",