You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5.2 KiB
5.2 KiB
| status |
|---|
| accepted |
客户目录 inbound seam:CustomerCatalogPort(crm-customer 声明并实现,crm-opportunity 调用)
Context
架构审查(/improve-codebase-architecture,2026-09-06 第三轮,候选 #1):商机详情「关联客户」子域对客户域只有 3 个只读调用点——候选池搜索(searchCustomers)、联系人级联(listCustomerContacts)、关联客户存在性校验(addCustomer 里的 getById)——却为此 import 了客户域两套宽服务接口 ICustomerService(8 方法 + CRUD)/ ICustomerContactService(8 方法 + CRUD)、实体 Customer、域内 CustomerSearchParam/CustomerSearchItemDTO。与 ADR-0031 修掉的 crm-lead 宽接口泄漏同形(方向相反:opportunity → customer)。
审查同时发现归档口径缝隙(本次一并拍板修复):
getById只滤@TableLogic软删、不滤archive_status——已归档客户可通过「关联客户存在」校验被新关联;CustomerMapper.searchForOpportunity的archiveStatus是可空<if>条件——商机侧没传时候选池含归档客户。
方向约束:crm-opportunity → crm-customer 为既定 Maven 单向依赖(对称 ADR-0029 记录的 lead 方向债),本 ADR 不改 pom 方向,只收窄跨域契约面。
Considered Options
- A(选中):crm-customer 声明窄 inbound port
CustomerCatalogPort(3 方法 + 随包 record)并自实现,crm-opportunity 只依赖该 port。 归档口径由端口钉死:search与requireLive一律只认archive_status=1 有效;宽服务退回客户域内部(本域 Controller 照用)。对称 ADR-0031 的ConvertibleLeadCatalogPort。 - B:保留宽服务注入,仅修归档缝隙。 契约面未收窄——两套 8 方法接口 + 实体 + 域内 param/DTO 仍在商机侧编译面,同 ADR-0031 选项 B 的否决理由,否。
- C:商机侧直查 customer 表。 跨域 DB 耦合、商机侧自己判定归档口径,违背 seam 精神(同 ADR-0025 选项 B 否决理由),否。
Decision
crm-customer 新增 com.crm.customer.port(既有扁平 port 包):
CustomerCatalogPort:接口 3 方法——PageResult<CustomerBrief> search(CustomerCatalogQuery)/List<ContactBrief> contactsOf(Long customerId)/CustomerBrief requireLive(Long customerId);- 随包 record:
CustomerBrief(id/编号/名称/类型/负责人名——名快照真源)、ContactBrief(contactId/name/phone/company/position——商机字段17级联列)、CustomerCatalogQuery(keyword/customerType/current/size/excludeCustomerIds;不含 archiveStatus,消费方无法放宽归档口径); CustomerCatalogException:unchecked,住 port 包;impl/CustomerCatalogPortImpl:@Service,委托ICustomerService/ICustomerContactService——search强制archiveStatus=ARCHIVE_STATUS_ACTIVE;requireLive委托getById后自查归档,null → 「客户不存在」、归档 → 「客户已归档,不可被关联」。
三项配套拍板:
- 归档口径:目录 = 有效客户目录。新关联拒归档(
requireLive);候选池拒归档(search强制有效);存量已关联行不动(不回溯清理)。排除已关联客户(excludeCustomerIds)是商机候选池语义,由消费方传入——端口不知 opportunity 表(同 ADR-0025 职责边界)。 - 宽服务方法处置:
ICustomerService.searchForOpportunity改名search留在本域(客户域 Controller 的POST /api/customer/search端点是第二消费方,HTTP 契约零变化;mapper 方法名不动)。 - 异常不跨 seam(ADR-0030 口径):商机侧 catch
CustomerCatalogException后翻译为自己的CODE_OPP_INVALID(66001)+「关联客户不存在或已被归档」。
crm-opportunity 侧 OpportunitySubServiceImpl 三调用点改注入 port,删全部 com.crm.customer.service.*/com.crm.customer.domain.* import;测试换 mock 单 port。
Consequences
- 商机侧对客户域的编译契约面从「两套宽服务(各 8 方法 + CRUD)+ 实体 + 域内 param/DTO」收窄到「3 方法 port + 随包 record」。宽接口演进不再迫使商机侧重编译;port 即商机侧的测试面(mock 单接口)。
- 行为收紧(本次拍板的产品口径):已归档客户不再出现在商机候选池、不可被新关联;存量已关联行不动。
addCustomer拒绝消息从「关联客户不存在或已被删除」改为「关联客户不存在或已被归档」(同一 66001 错误码)。 - 客户域本域消费不受影响:
listByCustomer(客户 Controller 在用)、search(HTTP 端点在用)留在宽服务。 - 测试:商机单测 mock 单 port(归档翻译路径用例);新增
CustomerCatalogPortImplIntegrationTest(H2)钉死归档口径——含「裸宽服务同条件返回全部、走 port 只返回有效」的对照断言(证明口径由 port 强制而非 mapper 天生)。 - 决策来源:
/improve-codebase-architecture审查(2026-09-06,候选 #1)+ grilling 七项拍板(归档口径 / 命名 / interface 形状 / 异常契约 / 机制归属 / 单票范围 / 留痕)。