实现下单任务网关与本地 worker
按 docs/order-gateway.md 落地:第三个部署单元 app.gateway(:31109)承担任务队列 + 状态镜像;本地 worker 在 app.trading.worker 内,按 RAKUTEN_ORDER_GATEWAY_URL 决定是否启动。规格 §5 最关键约束已守:租约过期绝不自动重投,恢复只能 reclaim, worker 收到 lease_count>1 时先核对站点订单。 站点交互(加购/下单/付款/订单列表反查)按规格 §10 留接口缝,site_interact.py 全部 NotImplementedError,verify.py 恒返回 unknown——等真实账号实测后再填, 不写猜测的提交逻辑。 310 个测试全绿,覆盖规格 §9 验收清单 12 条;架构测试守住三方互不 import。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+35
-13
@@ -1,12 +1,11 @@
|
||||
"""架构测试:守住抓取侧与交易侧的依赖方向
|
||||
"""架构测试:守住三个部署单元的依赖方向
|
||||
|
||||
拆成两个进程之后,最容易悄悄退化的不是功能而是边界——某天为了省事在交易侧
|
||||
`from app.scraping.parsers...` 一句,两个服务就重新长回一起:抓取的解析改动会
|
||||
牵动下单链路,交易服务也被迫加载整套抓取依赖(包括 Playwright)。
|
||||
三个进程之后,最容易悄悄退化的不是功能而是边界——某天为了省事在交易侧
|
||||
`from app.scraping.parsers...` 一句,两个服务就重新长回一起;或在 worker 里
|
||||
`from app.gateway.models...` 一句,本地的零依赖网关假设就破了。
|
||||
|
||||
允许的方向只有两条:scraping → shared、trading → shared。
|
||||
交易侧要用抓取的能力,走抓取服务的 HTTP 接口(`purchase` 块本来就是它的对外契约),
|
||||
不直接 import。
|
||||
允许的方向只有三条:scraping → shared、trading → shared、gateway → shared。
|
||||
任何两侧之间互不 import;需要对方能力时走 HTTP 接口。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -37,11 +36,17 @@ def _python_files(package: str) -> list[Path]:
|
||||
@pytest.mark.parametrize(
|
||||
("package", "forbidden"),
|
||||
[
|
||||
# 三个部署单元互不 import
|
||||
("scraping", "app.trading"),
|
||||
("scraping", "app.gateway"),
|
||||
("trading", "app.scraping"),
|
||||
# shared 是两侧的共同底座,反向依赖任何一侧都会形成环
|
||||
("trading", "app.gateway"),
|
||||
("gateway", "app.scraping"),
|
||||
("gateway", "app.trading"),
|
||||
# shared 是三方的共同底座,反向依赖任何一侧都会形成环
|
||||
("shared", "app.scraping"),
|
||||
("shared", "app.trading"),
|
||||
("shared", "app.gateway"),
|
||||
],
|
||||
)
|
||||
def test_package_does_not_import(package: str, forbidden: str):
|
||||
@@ -54,8 +59,9 @@ def test_package_does_not_import(package: str, forbidden: str):
|
||||
assert not offenders, f"{package} 不应依赖 {forbidden}:{offenders}"
|
||||
|
||||
|
||||
def test_both_entrypoints_build():
|
||||
"""两个入口都要能独立创建应用——这是「两个部署单元」的最低验收"""
|
||||
def test_all_three_entrypoints_build():
|
||||
"""三个入口都要能独立创建应用——这是「三个部署单元」的最低验收"""
|
||||
from app.gateway.main import create_app as create_gateway_app
|
||||
from app.scraping.main import create_app as create_scraping_app
|
||||
from app.trading.main import create_app as create_trading_app
|
||||
|
||||
@@ -63,11 +69,27 @@ def test_both_entrypoints_build():
|
||||
# 包成 _IncludedRouter 而非摊平,OpenAPI 反映的才是真正对外暴露的契约
|
||||
scraping_paths = set(create_scraping_app().openapi()["paths"])
|
||||
trading_paths = set(create_trading_app().openapi()["paths"])
|
||||
gateway_paths = set(create_gateway_app().openapi()["paths"])
|
||||
|
||||
# 各自的标志性接口都在
|
||||
assert "/api/search" in scraping_paths
|
||||
assert "/api/auth/status" in trading_paths
|
||||
# 登录态接口不该出现在抓取服务上:抓取实例可以多开,多份登录态就是重复下单的温床
|
||||
assert "/api/orders" in gateway_paths
|
||||
assert "/api/orders/lease" in gateway_paths
|
||||
|
||||
# 登录态接口不该出现在抓取/网关服务上:抓取实例可以多开、网关在服务器上暴露,
|
||||
# 多份登录态就是重复下单的温床
|
||||
assert not any(path.startswith("/api/auth") for path in scraping_paths)
|
||||
assert not any(path.startswith("/api/auth") for path in gateway_paths)
|
||||
# 抓取的站点接口不应出现在交易/网关上
|
||||
assert not any(path.startswith("/api/rakuma") for path in trading_paths)
|
||||
# /health 两边都有,各报各的
|
||||
assert "/health" in scraping_paths and "/health" in trading_paths
|
||||
assert not any(path.startswith("/api/rakuma") for path in gateway_paths)
|
||||
assert not any(path.startswith("/api/search") for path in trading_paths)
|
||||
assert not any(path.startswith("/api/search") for path in gateway_paths)
|
||||
# 下单任务接口只属于网关:trading 是本地 worker,不接收外部下单意图
|
||||
assert not any(path.startswith("/api/orders") for path in scraping_paths)
|
||||
assert not any(path.startswith("/api/orders") for path in trading_paths)
|
||||
# /health 三个服务都有,各报各的
|
||||
assert "/health" in scraping_paths
|
||||
assert "/health" in trading_paths
|
||||
assert "/health" in gateway_paths
|
||||
|
||||
Reference in New Issue
Block a user