wiring_academic.go 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. // academic 域装配(AC-11 交付部分)——跨服务依赖注入点。
  2. //
  3. // 为什么单独成文件:academic 的路由/Handler 由 AC-14 挂载,但**服务间注入关系**必须在
  4. // 本期固定下来,否则会退化成"端口永远没人实现 → 生产永远走降级分支"的静默缺陷。
  5. //
  6. // 本期唯一必须固定的注入(设计 §3.3 #23 / AC-11 硬要点 5):
  7. //
  8. // DivideService.firstCharged ← RecordRepo(FirstChargedRecordID)
  9. //
  10. // 背景:`divide_into_log_delete`(撤销分成)有两条分支——
  11. //
  12. // ① 学生存在"首次扣费上课记录" → **改绑 order_no 保留分成**(dump 口径,D11 已拍板);
  13. // ② 无首扣记录 → 回滚 total_income 并物理删除明细。
  14. //
  15. // 若端口未注入,DivideService 只能走 ②(保守分支)——即"撤销首扣订单"时把真实产生的分成整组冲掉
  16. // (少发钱,且与旧系统双跑必然不一致)。因此本文件的存在本身就是一条**回归防线**:
  17. // 谁把注入去掉,`TestWiringAcademicFirstChargedInjected` 立刻红(cmd/api 侧单测)。
  18. package main
  19. import (
  20. "gorm.io/gorm"
  21. academicrepo "yuansiwei/unified-backend/internal/academic/repository"
  22. academicservice "yuansiwei/unified-backend/internal/academic/service"
  23. )
  24. // 编译期契约断言(签名漂移即编译失败,杜绝"注入了但方法对不上"的隐蔽错误):
  25. //
  26. // ① RecordRepo 必须满足 DivideService 声明的 FirstChargedRecordReader(D11 改绑前提);
  27. // ② *DivideService 必须满足 RecordService 声明的 DivideLogGranter(结课联动分账前提)。
  28. var (
  29. _ academicservice.FirstChargedRecordReader = (*academicrepo.RecordRepo)(nil)
  30. _ academicservice.DivideLogGranter = (*academicservice.DivideService)(nil)
  31. )
  32. // academicServices academic 域已装配的服务集合(AC-14 接线时在此继续追加 Handler/路由)。
  33. type academicServices struct {
  34. // Divide 分账服务(AC-09;本章节只负责把首次扣费端口注入它)。
  35. Divide *academicservice.DivideService
  36. // FirstChargedInjected 首次扣费记录端口是否已注入(D11 改绑分支生效的判据,供启动日志与单测断言)。
  37. FirstChargedInjected bool
  38. }
  39. // newAcademicServices 装配 academic 域服务。
  40. //
  41. // 入参:gdb *gorm.DB——全局数据库句柄(连接池)。
  42. // 出参:*academicServices(永不返回 nil;装配失败由后续域初始化暴露)。
  43. //
  44. // 关键说明(**评审必看**):
  45. // - `NewRecordRepo(gdb, nil)` 的 nil 是**有意**的:FirstChargedRecordID 的取数口径
  46. // (dump `yuansiwei_finance.sql:763811`)按 student_id 精确过滤、无机构/校区维度,
  47. // 分账撤销场景也没有请求作用域(系统/后台动作)。该端口方法内部**不调用 Scoped**,
  48. // 因此 nil scope 安全;但这个 repo 实例**不得**用于任何带作用域的查询
  49. // (带作用域的方法一调用即 panic fail-fast,属预期行为)。
  50. // - 其余 academic 服务(Course/Record/Maintenance)需要请求作用域,由 Handler 层
  51. // `repository.NewXxxRepo(db, scope.FromFiber(c))` 按请求构造(AC-13/14),不在此处单例化。
  52. func newAcademicServices(gdb *gorm.DB) *academicServices {
  53. firstChargedReader := academicrepo.NewRecordRepo(gdb, nil)
  54. divide := academicservice.NewDivideService(gdb, firstChargedReader)
  55. return &academicServices{
  56. Divide: divide,
  57. FirstChargedInjected: true,
  58. }
  59. }