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.
345 lines
27 KiB
345 lines
27 KiB
|
14 hours ago
|
<!doctype html>
|
||
|
|
<html lang="zh-CN">
|
||
|
|
<head>
|
||
|
|
<meta charset="utf-8" />
|
||
|
|
<title>Architecture review — crm-backend-matt</title>
|
||
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
||
|
|
<script type="module">
|
||
|
|
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
||
|
|
mermaid.initialize({ startOnLoad: true, theme: "neutral", securityLevel: "loose" });
|
||
|
|
</script>
|
||
|
|
<style>
|
||
|
|
.seam { stroke-dasharray: 4 4; }
|
||
|
|
.leak { stroke: #dc2626; }
|
||
|
|
.deepbox { background: linear-gradient(135deg, #0f172a, #1e293b); }
|
||
|
|
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body class="bg-stone-50 text-slate-900 font-sans">
|
||
|
|
<main class="max-w-5xl mx-auto px-6 py-12 space-y-12">
|
||
|
|
|
||
|
|
<!-- ================= HEADER ================= -->
|
||
|
|
<header class="space-y-3">
|
||
|
|
<h1 class="text-3xl font-bold font-serif">Architecture review — crm-backend-matt</h1>
|
||
|
|
<div class="text-sm text-slate-500">2026-09-06 · 第三轮(当日)· 覆盖 13:30 报告之后的增量</div>
|
||
|
|
<div class="text-xs text-slate-500 leading-relaxed max-w-4xl">
|
||
|
|
扫描范围:票 11 商机侧切客户真源、客户导入返工(票 09-13 + audit-fixes)、票 12 工作计划、以及今晨两轮候选的落地验讫——
|
||
|
|
<b>已落地不再列</b>:OpportunityOplogWriter / CustomerPendingNoticeWriter / crm-base PendingNoticeWriter(13:30 #1/#3,工作树已收编,CONTEXT.md 已记)、
|
||
|
|
CustomerH2Schema / CustomerMapper 单一事实源 / 客户三 workspace 口径(晨报 #1/#2)。
|
||
|
|
ADR-0030 / 0031 / 0029 决策不重开。
|
||
|
|
</div>
|
||
|
|
<div class="flex flex-wrap gap-4 text-xs text-slate-600 pt-1">
|
||
|
|
<span class="flex items-center gap-1"><span class="inline-block w-4 h-3 border-2 border-slate-500 rounded-sm"></span> module</span>
|
||
|
|
<span class="flex items-center gap-1"><span class="inline-block w-4 border-t-2 border-dashed border-slate-500"></span> seam</span>
|
||
|
|
<span class="flex items-center gap-1"><span class="inline-block w-4 border-t-2 border-red-600"></span> 泄漏(跨 seam 的非契约知识)</span>
|
||
|
|
<span class="flex items-center gap-1"><span class="inline-block w-4 h-3 deepbox rounded-sm"></span> deep module</span>
|
||
|
|
</div>
|
||
|
|
</header>
|
||
|
|
|
||
|
|
<section id="candidates" class="space-y-10">
|
||
|
|
|
||
|
|
<!-- ================= CANDIDATE 1 ================= -->
|
||
|
|
<article id="c1">
|
||
|
|
<div class="bg-white rounded-2xl shadow-lg overflow-hidden">
|
||
|
|
<div class="bg-emerald-700 text-white px-8 py-5 flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<div class="text-xs text-emerald-200 mb-1">候选 #1 · 宽 interface 泄漏复发(ADR-0031 同形病灶,客户侧)</div>
|
||
|
|
<h2 class="text-2xl font-bold">商机读客户:三个只读需求,拖进两套宽 service + 实体 + param</h2>
|
||
|
|
</div>
|
||
|
|
<div class="flex gap-2 items-center">
|
||
|
|
<span class="bg-emerald-500 text-white text-sm px-3 py-1 rounded-lg font-bold">Strong</span>
|
||
|
|
<span class="bg-white/20 text-white text-xs px-2 py-1 rounded-lg">seam + adapter</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="px-8 py-6 space-y-6">
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">涉及文件</h3>
|
||
|
|
<ul class="text-xs mono space-y-1 text-slate-700">
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-opportunity/.../service/impl/OpportunitySubServiceImpl.java · 票 11 切真源:注入 ICustomerService + ICustomerContactService</li>
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-customer/.../service/ICustomerService.java · searchForOpportunity(javadoc 自述「商机侧客户搜索」)</li>
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-customer/.../service/ICustomerContactService.java · listByCustomer / search(javadoc 均点名「商机字段17级联」「商机关键联系人数据源」)</li>
|
||
|
|
<li class="bg-red-50 border border-red-200 text-red-700 rounded px-2 py-1">crm-customer/.../port/ · 已有 6 个 port(含 2 个 outbound 商机 port),唯独缺商机要用的 inbound 目录 port</li>
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">Before / After</h3>
|
||
|
|
<div class="grid md:grid-cols-2 gap-4">
|
||
|
|
<div class="border-2 border-red-300 rounded-xl p-4 bg-red-50/40">
|
||
|
|
<div class="text-xs font-bold text-red-700 mb-2">BEFORE · 无 seam 直连宽 service</div>
|
||
|
|
<pre class="mermaid text-xs">
|
||
|
|
flowchart LR
|
||
|
|
SUB["OpportunitySubServiceImpl<br/>(615 行 grab-bag)"]
|
||
|
|
CS["ICustomerService<br/>8 方法 + MP 全套 CRUD"]
|
||
|
|
CT["ICustomerContactService<br/>8 方法 + MP 全套 CRUD"]
|
||
|
|
E1["Customer 实体(30+ 字段)<br/>CustomerSearchParam 手工构造"]
|
||
|
|
E2["CustomerSearchItemDTO / ContactDTO<br/>逐字段映射"]
|
||
|
|
SUB -->|"getById(取快照)"| CS
|
||
|
|
SUB -->|"searchForOpportunity"| CS
|
||
|
|
SUB -->|"listByCustomer"| CT
|
||
|
|
CS --> E1
|
||
|
|
CS --> E2
|
||
|
|
CT --> E2
|
||
|
|
classDef leak stroke:#dc2626,stroke-width:2px;
|
||
|
|
class E1,E2 leak
|
||
|
|
</pre>
|
||
|
|
<div class="text-center text-red-700 font-bold text-[11px] mt-1">3 个只读调用点 → 必须理解 16+ 方法、2 个实体、2 组 param/DTO</div>
|
||
|
|
</div>
|
||
|
|
<div class="border-2 border-emerald-300 rounded-xl p-4 bg-emerald-50/40">
|
||
|
|
<div class="text-xs font-bold text-emerald-700 mb-2">AFTER · 窄目录 port(住 provider 侧,对称 ADR-0031)</div>
|
||
|
|
<div class="space-y-2 text-xs mono">
|
||
|
|
<div class="bg-white border border-slate-300 rounded p-2">crm-opportunity:OpportunitySubServiceImpl(薄 adapter)</div>
|
||
|
|
<div class="text-center text-slate-400">↓ 小 interface(3 方法,brief 视图)</div>
|
||
|
|
<div class="deepbox text-white rounded-lg px-3 py-3">
|
||
|
|
<div class="text-[10px] uppercase tracking-wider text-slate-300">deep module(crm-customer · port/inbound)</div>
|
||
|
|
<div class="font-bold">CustomerCatalogPort</div>
|
||
|
|
<div class="text-[10px] text-slate-300 mt-1">search(query) → Page<CustomerBrief><br/>contactsOf(customerId) → List<ContactBrief><br/>requireLive(customerId) → CustomerBrief</div>
|
||
|
|
<div class="text-[10px] text-slate-400 mt-1 border-t border-slate-600 pt-1">实现吸收:Customer 实体读取 / 归档与删除口径 / param 构造与 DTO 映射</div>
|
||
|
|
</div>
|
||
|
|
<div class="text-center text-emerald-700 font-bold text-[11px]">「已归档客户能不能被关联」从隐式口径变成 port 的一行契约</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="grid md:grid-cols-2 gap-6 text-sm">
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2">问题</h3>
|
||
|
|
<p class="text-slate-700 leading-relaxed">商机侧只做三件事——存在性校验取名快照、候选池搜索、联系人级联——却要越过 seam 理解两套宽 service 外加 MyBatis-Plus 全套 CRUD、Customer 实体 30+ 字段、两个 param/DTO 的构造与映射。口径还藏在实现里:<span class="mono text-xs">getById</span> 只滤 @TableLogic deleted、不滤 archive_status(归档=删除语义),<b>已归档客户照样通过「关联客户存在」校验</b>——消费方从 interface 上无从知道。provider 侧同样受害:宽 service 上长出 searchForOpportunity / listByCustomer / search 三个「为商机而生」的方法,消费方的词汇进了 provider 的 interface。这正是 ADR-0031 修掉的病灶(当时的对象是 crm-lead),在客户侧原样复发。</p>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2">方案(plain English)</h3>
|
||
|
|
<p class="text-slate-700 leading-relaxed">crm-customer 的 port/ 目录新增一个窄 inbound 目录 port(照抄 ConvertibleLeadCatalogPort 的形状:provider 定义、消费方只见 brief 视图):搜索、按客户取联系人、取存活客户摘要三个方法;归档/删除口径、param 构造、实体→brief 映射全部收进实现;宽 service 上的三个商机词汇方法收编(转发或删除)。商机侧注入 port,删掉对 ICustomerService / ICustomerContactService / Customer 实体的全部 import。</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="rounded-lg bg-amber-50 border-l-4 border-amber-500 px-4 py-3 text-sm text-amber-800">
|
||
|
|
<b>ADR 关系(非冲突)</b>:与 ADR-0031 同形而非相抵——那次修 crm-lead 宽接口泄漏的结论就是「收窄跨域契约」;D14 依赖方向不变(port 住 provider 侧,crm-customer 不反向依赖)。另外它与候选 #3 有顺序耦合:先立此 seam 再拆 SubService,避免拆分把客户映射复制七份。
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">收益</h3>
|
||
|
|
<div class="flex flex-wrap gap-2 text-xs">
|
||
|
|
<span class="bg-emerald-50 text-emerald-700 border border-emerald-200 px-2 py-1 rounded">interface:16+ 方法 + 2 实体 → 3 方法 + 2 brief</span>
|
||
|
|
<span class="bg-emerald-50 text-emerald-700 border border-emerald-200 px-2 py-1 rounded">归档/删除口径收进一处实现</span>
|
||
|
|
<span class="bg-emerald-50 text-emerald-700 border border-emerald-200 px-2 py-1 rounded">leverage:A5 项目选客户 = 第二个消费方只抄 port</span>
|
||
|
|
<span class="bg-emerald-50 text-emerald-700 border border-emerald-200 px-2 py-1 rounded">测试面:mock 一个 port,不再造 Customer 实体</span>
|
||
|
|
<span class="bg-emerald-50 text-emerald-700 border border-emerald-200 px-2 py-1 rounded">provider 宽 service 摘掉商机词汇</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</article>
|
||
|
|
|
||
|
|
<!-- ================= CANDIDATE 2 ================= -->
|
||
|
|
<article id="c2">
|
||
|
|
<div class="bg-white rounded-2xl shadow-lg overflow-hidden">
|
||
|
|
<div class="bg-amber-600 text-white px-8 py-5 flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<div class="text-xs text-amber-100 mb-1">候选 #2 · 「同口径」靠复制与字符串前缀维持</div>
|
||
|
|
<h2 class="text-2xl font-bold">客户导入:判定机制已单点,但解析/计数/软提示仍是约定接缝</h2>
|
||
|
|
</div>
|
||
|
|
<div class="flex gap-2 items-center">
|
||
|
|
<span class="bg-amber-500 text-white text-sm px-3 py-1 rounded-lg font-bold">Worth exploring</span>
|
||
|
|
<span class="bg-white/20 text-white text-xs px-2 py-1 rounded-lg">in-process</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="px-8 py-6 space-y-6">
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">涉及文件</h3>
|
||
|
|
<ul class="text-xs mono space-y-1 text-slate-700">
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-customer/.../impl/CustomerImportServiceImpl.java · upload():Excel 双 sheet 解析 + 判定计数 + 预览组装(361 行)</li>
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-customer/.../task/CustomerImportExecutor.java · doImport():同一解析再写一遍 + 再计数一遍(355 行)</li>
|
||
|
|
<li class="bg-emerald-50 rounded px-2 py-1">crm-customer/.../impl/CustomerImportAnalyzer.java · 判定本体单一实现 ✓(320 行,deletion test 通过,不动)</li>
|
||
|
|
<li class="bg-red-50 border border-red-200 text-red-700 rounded px-2 py-1">.scratch/customer-e2e/_import-heavy-r2.xlsx / _import-incr-r2.xlsx · 测试被迫造真实 xlsx 走 HTTP e2e,行级单元无法直接喂入</li>
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">Before / After(横剖:同一机制带,两条路径各织一遍)</h3>
|
||
|
|
<div class="grid md:grid-cols-2 gap-4">
|
||
|
|
<div class="border-2 border-red-300 rounded-xl p-4 bg-red-50/40">
|
||
|
|
<div class="text-xs font-bold text-red-700 mb-3">BEFORE · upload 与 confirm 两条路径各持一份「解析 + 计数」</div>
|
||
|
|
<div class="grid grid-cols-2 gap-3 text-[11px] mono">
|
||
|
|
<div class="space-y-1">
|
||
|
|
<div class="font-bold text-slate-700">upload() 预览路径</div>
|
||
|
|
<div class="border-l-4 border-red-400 pl-2 py-1 bg-white rounded">EasyExcel 双 sheet 解析(逐字复制品 A)</div>
|
||
|
|
<div class="border-l-4 border-slate-400 pl-2 py-1 bg-white rounded">analyzer.analyzeCustomers/Contacts ✓</div>
|
||
|
|
<div class="border-l-4 border-red-400 pl-2 py-1 bg-white rounded">verdict 计数 switch(副本 A)</div>
|
||
|
|
</div>
|
||
|
|
<div class="space-y-1">
|
||
|
|
<div class="font-bold text-slate-700">executor.doImport() 执行路径</div>
|
||
|
|
<div class="border-l-4 border-red-400 pl-2 py-1 bg-white rounded">EasyExcel 双 sheet 解析(逐字复制品 B)</div>
|
||
|
|
<div class="border-l-4 border-slate-400 pl-2 py-1 bg-white rounded">analyzer.analyzeCustomers/Contacts ✓</div>
|
||
|
|
<div class="border-l-4 border-red-400 pl-2 py-1 bg-white rounded">verdict 计数 switch(副本 B)</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="mt-3 bg-white border-2 border-red-400 rounded p-2 mono text-[11px]">
|
||
|
|
<div class="text-red-700 font-bold">字符串前缀协议(泄漏)</div>
|
||
|
|
<div>executor:<span class="text-red-700">p.getReason().startsWith("疑似重复")</span><br/>analyzer 用展示文案向 executor 传「本行 INSERT 且带软提示」——文案一改,疑似重复明细静默消失</div>
|
||
|
|
</div>
|
||
|
|
<div class="text-center text-red-700 font-bold text-[11px] mt-2">另:isBlank / trimOrNull / clip500 三个文件各一份私有拷贝</div>
|
||
|
|
</div>
|
||
|
|
<div class="border-2 border-emerald-300 rounded-xl p-4 bg-emerald-50/40">
|
||
|
|
<div class="text-xs font-bold text-emerald-700 mb-3">AFTER · 导入底座收编解析与契约,analyzer 不动</div>
|
||
|
|
<div class="space-y-2 text-[11px] mono">
|
||
|
|
<div class="grid grid-cols-2 gap-2">
|
||
|
|
<div class="bg-white border border-slate-300 rounded p-2">upload()<br/><span class="text-slate-500">只剩守门 + 任务落库</span></div>
|
||
|
|
<div class="bg-white border border-slate-300 rounded p-2">executor<br/><span class="text-slate-500">只剩行落库 + 计数回写</span></div>
|
||
|
|
</div>
|
||
|
|
<div class="text-center text-slate-400">↓</div>
|
||
|
|
<div class="deepbox text-white rounded-lg px-3 py-2">
|
||
|
|
<div class="font-bold">CustomerImportReader(新)</div>
|
||
|
|
<div class="text-slate-300">bytes → CustomerSheet + ContactSheet + 模板表头校验(单点)</div>
|
||
|
|
</div>
|
||
|
|
<div class="bg-white border border-emerald-400 rounded p-2">CustomerImportAnalyzer(不动)<br/><span class="text-emerald-700">ContactPlan 新增显式 <b>softSuspect</b> 字段 → 字符串协议废除</span></div>
|
||
|
|
<div class="bg-white border border-emerald-400 rounded p-2">tally(plans) 单点计数(预览与执行共用)</div>
|
||
|
|
</div>
|
||
|
|
<div class="text-center text-emerald-700 font-bold text-[11px] mt-2">预览与执行「同口径」从注释约定变成同一份代码</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="grid md:grid-cols-2 gap-6 text-sm">
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2">问题</h3>
|
||
|
|
<p class="text-slate-700 leading-relaxed">判定语义已正确单点化(analyzer 过 deletion test:删掉它,匹配/查重/冲突规则会在 upload 与 executor 各复活一份),但机制的四周边缘仍是约定接缝:workbook 解析两处逐字复制(改一处另一处必然漂移)、verdict 计数两份 switch、软提示靠 reason 字符串前缀跨模块传递。模板演进(加列、加 sheet)时要同时改对四处。</p>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2">方案(plain English)与保留意见</h3>
|
||
|
|
<p class="text-slate-700 leading-relaxed">抽一个 <span class="mono text-xs">CustomerImportReader</span> module:bytes→两 sheet 行 + 模板表头校验单点,upload 与 executor 共用;ContactPlan 加显式 softSuspect boolean(结构化契约替代字符串前缀);计数 tally 单点。<b>收敛面克制</b>:不收判定语义(analyzer 已对)、不收行落库(executor 的真差异)。</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">收益</h3>
|
||
|
|
<div class="flex flex-wrap gap-2 text-xs">
|
||
|
|
<span class="bg-amber-50 text-amber-700 border border-amber-200 px-2 py-1 rounded">locality:模板演进只改一处</span>
|
||
|
|
<span class="bg-amber-50 text-amber-700 border border-amber-200 px-2 py-1 rounded">字符串协议 → 结构化字段,文案改动不丢数据</span>
|
||
|
|
<span class="bg-amber-50 text-amber-700 border border-amber-200 px-2 py-1 rounded">测试面:直接喂 rows/plans,不再造 xlsx</span>
|
||
|
|
<span class="bg-amber-50 text-amber-700 border border-amber-200 px-2 py-1 rounded">「同口径」从 javadoc 注释变成同一份代码</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</article>
|
||
|
|
|
||
|
|
<!-- ================= CANDIDATE 3 ================= -->
|
||
|
|
<article id="c3">
|
||
|
|
<div class="bg-white rounded-2xl shadow-lg overflow-hidden">
|
||
|
|
<div class="bg-amber-600 text-white px-8 py-5 flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<div class="text-xs text-amber-100 mb-1">候选 #3 · 13:30 遗留(#4)且持续加重 —— 诚实标注:拆分换 locality,不是深化</div>
|
||
|
|
<h2 class="text-2xl font-bold">IOpportunitySubService:23 方法 / 7 子域 grab-bag,仍是每张新 Tab 的落点</h2>
|
||
|
|
</div>
|
||
|
|
<div class="flex gap-2 items-center">
|
||
|
|
<span class="bg-amber-500 text-white text-sm px-3 py-1 rounded-lg font-bold">Worth exploring</span>
|
||
|
|
<span class="bg-white/20 text-white text-xs px-2 py-1 rounded-lg">in-process</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="px-8 py-6 space-y-6">
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">涉及文件(数字自 13:30 后继续上涨)</h3>
|
||
|
|
<ul class="text-xs mono space-y-1 text-slate-700">
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-opportunity/.../service/IOpportunitySubService.java · 21 → <b>23</b> 方法(+searchCustomers +listCustomerContacts)</li>
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-opportunity/.../impl/OpportunitySubServiceImpl.java · 599 → <b>615</b> 行,且新增跨模块客户依赖</li>
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">OpportunitySubServiceImplTest.java · 613+ 行全子域 mock</li>
|
||
|
|
<li class="bg-red-50 border border-red-200 text-red-700 rounded px-2 py-1">近两周每张新 Tab 票都落进这个文件:票 11 切真源 ✓、票 12 工作计划 ✓ —— 引力仍在</li>
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">Before / After(质量图:interface 面积 vs implementation 面积)</h3>
|
||
|
|
<div class="grid md:grid-cols-2 gap-4">
|
||
|
|
<div class="border-2 border-red-300 rounded-xl p-4 bg-red-50/40">
|
||
|
|
<div class="text-xs font-bold text-red-700 mb-3">BEFORE · grab-bag:interface 几乎与 implementation 一样宽</div>
|
||
|
|
<div class="flex items-end gap-4 h-52 px-4">
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1">
|
||
|
|
<div class="w-full bg-red-400 border-2 border-red-500 rounded-t" style="height:170px"></div>
|
||
|
|
<div class="text-[10px] text-red-700 font-bold">interface · 23 方法 / 7 子域</div>
|
||
|
|
</div>
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1">
|
||
|
|
<div class="w-full bg-slate-700 border-2 border-slate-800 rounded-t" style="height:195px"></div>
|
||
|
|
<div class="text-[10px] text-slate-700 font-bold">impl · 615 行 / 测试 613+ 行</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="text-center text-red-700 font-bold text-[11px] mt-2">deletion test 不过:删掉它 23 个签名只会搬家</div>
|
||
|
|
</div>
|
||
|
|
<div class="border-2 border-emerald-300 rounded-xl p-4 bg-emerald-50/40">
|
||
|
|
<div class="text-xs font-bold text-emerald-700 mb-3">AFTER · 按子域拆:小 interface 背一小块内聚实现</div>
|
||
|
|
<div class="flex items-end gap-2 h-52 px-2">
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1"><div class="w-full bg-emerald-500 rounded-t" style="height:48px"></div><div class="text-[9px] text-slate-600">客户关联<br/>(先过 #1 port)</div></div>
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1"><div class="w-full bg-emerald-500 rounded-t" style="height:32px"></div><div class="text-[9px] text-slate-600">跟进</div></div>
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1"><div class="w-full bg-emerald-500 rounded-t" style="height:25px"></div><div class="text-[9px] text-slate-600">勘察</div></div>
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1"><div class="w-full bg-emerald-500 rounded-t" style="height:25px"></div><div class="text-[9px] text-slate-600">附件</div></div>
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1"><div class="w-full bg-emerald-500 rounded-t" style="height:46px"></div><div class="text-[9px] text-slate-600">团队</div></div>
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1"><div class="w-full bg-emerald-500 rounded-t" style="height:36px"></div><div class="text-[9px] text-slate-600">工作计划</div></div>
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1"><div class="w-full bg-emerald-500 rounded-t" style="height:14px"></div><div class="text-[9px] text-slate-600">日志读</div></div>
|
||
|
|
</div>
|
||
|
|
<div class="text-center text-emerald-700 font-bold text-[11px] mt-2">每片小实现 —— 赢 locality,不冒称 depth</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="grid md:grid-cols-2 gap-6 text-sm">
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2">问题</h3>
|
||
|
|
<p class="text-slate-700 leading-relaxed">Grab-bag 不是 deep module(deletion test 不过),真正的摩擦是 navigability 与冲突面:商机是持续最热的整改区,每动一个 Tab 都面对 615 行 impl + 613 行全子域 mock 的测试文件;现在还背着跨模块客户知识(候选 #1 的病灶就住在这个文件里),两个候选在此叠加。</p>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2">方案(plain English)与顺序</h3>
|
||
|
|
<p class="text-slate-700 leading-relaxed">按 Tab 子域拆 service(每域 2–4 方法小 interface + 自家 impl + 自家测试文件),controller 端点不动。<b>顺序:先做候选 #1</b>(客户知识收进 port),再拆——否则拆分会把客户映射与归档口径复制进每个新 impl。</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">收益</h3>
|
||
|
|
<div class="flex flex-wrap gap-2 text-xs">
|
||
|
|
<span class="bg-amber-50 text-amber-700 border border-amber-200 px-2 py-1 rounded">locality:子域改动封闭</span>
|
||
|
|
<span class="bg-amber-50 text-amber-700 border border-amber-200 px-2 py-1 rounded">测试按域分文件,mock 面缩到域内</span>
|
||
|
|
<span class="bg-amber-50 text-amber-700 border border-amber-200 px-2 py-1 rounded">热点模块合并冲突面缩小</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</article>
|
||
|
|
|
||
|
|
<!-- ================= 遗留(前轮在案,未动,不重画) ================= -->
|
||
|
|
<section>
|
||
|
|
<div class="bg-white rounded-2xl shadow border border-slate-200 overflow-hidden">
|
||
|
|
<div class="bg-slate-500 text-white px-8 py-4">
|
||
|
|
<div class="text-xs text-slate-300 mb-1">前轮在案 · 未动 · 仍然成立</div>
|
||
|
|
<h2 class="text-xl font-bold">两项遗留,不另画图</h2>
|
||
|
|
</div>
|
||
|
|
<div class="px-8 py-5 text-sm text-slate-700 leading-relaxed space-y-2">
|
||
|
|
<p><b>自定义视图操作符 kit</b>(13:30 #2,Strong 未动):operator 词汇表仍 6 vs 8 两处各持(商机/客户),注入防护各一份;线索作为第 3 个消费方仍在途。做前需先拍「机制归属 crm-preference」这一条。</p>
|
||
|
|
<p><b>crm-rule 六个权限种子 Initializer 合并为声明式单 runner</b>(晨报 #3 / 13:30 #5,未动):随时小票,第 7 个规则菜单边际成本 45 行 → 1 行。</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<!-- ================= TOP RECOMMENDATION ================= -->
|
||
|
|
<section id="top-recommendation">
|
||
|
|
<div class="bg-gradient-to-r from-emerald-700 to-emerald-600 text-white rounded-2xl shadow-lg px-8 py-8">
|
||
|
|
<div class="text-xs text-emerald-200 mb-2">TOP RECOMMENDATION</div>
|
||
|
|
<h2 class="text-2xl font-bold mb-3"><a href="#c1" class="underline decoration-emerald-300">先做 #1:客户目录 port(CustomerCatalogPort)</a></h2>
|
||
|
|
<p class="leading-relaxed text-emerald-50 max-w-4xl">
|
||
|
|
理由:(a) <b>同形修法在本仓库已赢两次</b>——ADR-0031 ConvertibleLeadCatalogPort(同样修宽接口泄漏)与 crm-customer port/ 目录六个既有 port,这是照抄已赢的棋;(b) <b>摩擦刚发生且消费方还在长</b>:票 11 刚把宽 service 接进来,A5 项目选客户、商机关键联系人真源化都会成为下一个调用点——seam 每早立一天,宽 interface 的债就少涨一截;(c) 顺手把「已归档客户可被关联」这类口径问题从隐式变显式(requireLive 一行契约);(d) 测试面立竿见影:OpportunitySubServiceImplTest 从 mock 两套宽 service + 构造 Customer 实体,换成 mock 一个 3 方法 port。<br/>
|
||
|
|
随后顺序:#2(小票,随时)→ #3(必须在 #1 之后拆)· 遗留两项独立。
|
||
|
|
</p>
|
||
|
|
<div class="mt-4 text-xs text-emerald-200">建议顺序:#1 → #2 → #3(在 #1 后)· 操作符 kit 先拍归属再动 · Initializer 随时小票</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
</main>
|
||
|
|
</body>
|
||
|
|
</html>
|