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

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)。

审查同时发现归档口径缝隙(本次一并拍板修复):

  1. getById 只滤 @TableLogic 软删、不滤 archive_status——已归档客户可通过「关联客户存在」校验被新关联
  2. CustomerMapper.searchForOpportunityarchiveStatus 是可空 <if> 条件——商机侧没传时候选池含归档客户

方向约束:crm-opportunity → crm-customer 为既定 Maven 单向依赖(对称 ADR-0029 记录的 lead 方向债),本 ADR 不改 pom 方向,只收窄跨域契约面

Considered Options

  • A(选中):crm-customer 声明窄 inbound port CustomerCatalogPort(3 方法 + 随包 record)并自实现,crm-opportunity 只依赖该 port。 归档口径由端口钉死:searchrequireLive 一律只认 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_ACTIVErequireLive 委托 getById 后自查归档,null → 「客户不存在」、归档 → 「客户已归档,不可被关联」。

三项配套拍板:

  1. 归档口径:目录 = 有效客户目录。新关联拒归档(requireLive);候选池拒归档(search 强制有效);存量已关联行不动(不回溯清理)。排除已关联客户(excludeCustomerIds)是商机候选池语义,由消费方传入——端口不知 opportunity 表(同 ADR-0025 职责边界)。
  2. 宽服务方法处置ICustomerService.searchForOpportunity 改名 search 留在本域(客户域 Controller 的 POST /api/customer/search 端点是第二消费方,HTTP 契约零变化;mapper 方法名不动)。
  3. 异常不跨 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 形状 / 异常契约 / 机制归属 / 单票范围 / 留痕)。