Go E07:workspace 与多 module 能否隐藏依赖
E06在同一个累计任务服务上比较了编译配置。若外部工具也要提交并查询任务,是否应该让它直接导入服务的 main 包?不能:服务包封装了执行器、SQL 和诊断端点,消费者只需要稳定的 HTTP 请求/响应契约。本篇从累计工程中拆出 client/:只实现 Submit、Lookup、Cancel 的薄客户端。它不接触任务状态机,更不会复制 worker。代码、原始双流和真实数据库实验见完整素材包、SHA-256及独立复跑说明。
一个成功的 workspace 构建证明不了依赖闭合
冻结 Go 1.27.1。go help work 规定 go.work 的 use 把列出的 module 当作工作区根模块,并允许工作区替换覆盖模块内的替换。累计工程的 go.work 列出原服务、client/ 和 consumer/;后者声明 require example.com/go-language-task/client v0.1.0,却没有独立的解析途径。GOWORK=auto 从 consumer/ 构建通过,只说明这三份源码在同一个目录树中能协同编译,不说明独立消费者能拿到这个版本。
把客户端与消费者复制到新的临时目录,不复制 go.work。同一份消费者运行 GOWORK=off GOPROXY=off go build -mod=mod 非零退出:依赖下载被禁用,example.com/go-language-task/client v0.1.0 无法解析,并且没有生成二进制。这个失败比 workspace 内的一次绿色构建更能说明问题。随后只修改消费者的临时 go.mod,加入明确的 replace example.com/go-language-task/client => ../client;再次以 GOWORK=off GOPROXY=off 编译通过。独立构建依赖一份显式的本地源码映射;仓库并没有发布远程 v0.1.0,也不能把这项实验写成「任何读者无需客户端源码就能下载」。正式发布前需提供可取得的版本、模块校验和兼容验证。
编译兼容之后,仍须对业务结论核账
客户端向原任务服务发送 JSON POST /tasks,只有收到 202 并读出非空 ID 才返回;202 不是任务成功。独立消费者用这个 ID 轮询 GET /tasks/{id},直到查到 SUCCEEDED/words=2;另用 PostgreSQL 查询实际 task[0]|SUCCEEDED|2。调用上下文与 HTTP 超时受消费者控制,错误响应保留 HTTP 状态码,不把未知 ID 404 变成一个成功的空任务记录。这个客户端适合需要远程提交任务的独立进程;并没有必要把完整 SQL/worker 模块提到一个共享库。
可编译也不等于所有旧源调用都仍可编译:consumer 冻结了 Submit(ctx,text) 的使用;另一份使用 LegacySubmit 的不兼容源代码在脱离 workspace 的同一客户端副本里得到编译错误和非零退出。这里比较的是当前源码签名与两种消费者,不是两个真实已发布版本之间的升级测试;v0.1.0 只是本地测试所需的 require 字面量。未来一旦改名或改变错误/状态的语义,要另做老版本依赖与行为回归。go.work 不能替代表达和验证这些承诺。
练习:①工作区 go build 通过后能删去消费者对客户端的 require 吗?答:不能;应先关闭 workspace,在独立目录重新解析依赖,确认版本有可取得的来源。②添加本地 replace 就是发布 v0.1.0 吗?答:不是,证据只证明持有两份本地源码时可构建;正式发布与远端下载仍未运行。③202 与查询终态之间如果网络中断,可以重发提交并声称不会重复吗?答:不行;没有提交幂等键,受理与结果间的未知窗口须先查询或依赖另行约定的去重机制,不能靠客户端重试次数推断。
本章还在独立 PostgreSQL 16.15 上复跑累计服务两包测试和 race;无 race 报告只覆盖这些执行路径。外部 module 发布、代理下载、其他平台、全站构建及部署 NOT_RUN。E08继续追问多个实例如何处理一条已领取任务。
参考资料
- Go 1.27.1
go help work和go help modules:原始一手帮助、源码 SHA、实际版本映射与正反构建在evidence/E07/。






