Files
pig-farm-controller/design/verification-before-device-deletion/plan_service_refactor_to_domain.md
2025-11-02 23:26:16 +08:00

9.9 KiB
Raw Blame History

重构方案:将 app/service/plan_service.go 的核心逻辑迁移到 domain/plan/plan_service.go

目标:

  • app/service/plan_service.go (应用服务层): 仅负责接收 DTO、将 DTO 转换为领域实体、调用 domain/plan/plan_service 的领域方法,并将领域方法返回的领域实体转换为 DTO 返回。
  • domain/plan/plan_service.go (领域层): 封装所有与计划相关的业务逻辑、验证规则、状态管理以及对领域实体的查询操作。

详细步骤:

第一步:修改 domain/plan/plan_service.go (领域层)

  1. 引入必要的依赖:

    • repository.PlanRepository:用于与计划数据存储交互。
    • repository.DeviceRepository:如果计划逻辑中需要设备信息。
    • models.Plan:领域实体。
    • errorsgorm.ErrRecordNotFound:用于错误处理。
    • models.PlanTypeSystem, models.PlanStatusEnabled, models.PlanContentTypeSubPlans, models.PlanContentTypeTasks 等常量。
    • git.huangwc.com/pig/pig-farm-controller/internal/infra/models
    • git.huangwc.com/pig/pig-farm-controller/internal/infra/repository
    • errors
    • gorm.io/gorm
  2. 定义领域层错误: 将 app/service/plan_service.go 中定义的错误(ErrPlanNotFound, ErrPlanCannotBeModified 等)迁移到 domain/plan/plan_service.go,并根据领域层的语义进行调整。

    var (
    	// ErrPlanNotFound 表示未找到计划
    	ErrPlanNotFound = errors.New("计划不存在")
    	// ErrPlanCannotBeModified 表示计划不允许修改
    	ErrPlanCannotBeModified = errors.New("系统计划不允许修改")
    	// ErrPlanCannotBeDeleted 表示计划不允许删除
    	ErrPlanCannotBeDeleted = errors.New("系统计划不允许删除")
    	// ErrPlanCannotBeStarted 表示计划不允许手动启动
    	ErrPlanCannotBeStarted = errors.New("系统计划不允许手动启动")
    	// ErrPlanAlreadyEnabled 表示计划已处于启动状态
    	ErrPlanAlreadyEnabled = errors.New("计划已处于启动状态,无需重复操作")
    	// ErrPlanNotEnabled 表示计划未处于启动状态
    	ErrPlanNotEnabled = errors.New("计划当前不是启用状态")
    	// ErrPlanCannotBeStopped 表示计划不允许停止
    	ErrPlanCannotBeStopped = errors.New("系统计划不允许停止")
    )
    
  3. 扩展 plan.Service 接口:

    • app/service/plan_service.goPlanService 接口的所有方法(CreatePlan, GetPlanByID, ListPlans, UpdatePlan, DeletePlan, StartPlan, StopPlan)添加到 domain/plan/Service 接口中。
    • 这些方法的参数和返回值将直接使用领域实体(*models.Plan)或基本类型,而不是 DTO。例如
      • CreatePlan(plan *models.Plan) (*models.Plan, error)
      • GetPlanByID(id uint) (*models.Plan, error)
      • ListPlans(opts repository.ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error)
      • UpdatePlan(plan *models.Plan) (*models.Plan, error)
      • DeletePlan(id uint) error
      • StartPlan(id uint) error
      • StopPlan(id uint) error
  4. 修改 planServiceImpl 结构体:

    • 添加 planRepo repository.PlanRepository 字段。
    • 添加 deviceRepo repository.DeviceRepository 字段 (如果需要)。
    • analysisPlanTaskManager plan.AnalysisPlanTaskManager 字段保持不变。
    type planServiceImpl struct {
    	executionManager ExecutionManager
    	taskManager      AnalysisPlanTaskManager
    	planRepo         repository.PlanRepository // 新增
    	// deviceRepo       repository.DeviceRepository // 如果需要,新增
    	logger           *logs.Logger
    }
    
  5. 实现 plan.Service 接口中的新方法:

    • app/service/plan_service.goplanService 的所有业务逻辑方法(CreatePlan, GetPlanByID, ListPlans, UpdatePlan, DeletePlan, StartPlan, StopPlan)的实现,迁移到 domain/plan/planServiceImpl 中。
    • 关键修改点:
      • 参数和返回值: 确保这些方法现在接收和返回的是 *models.Plan 或其他领域实体,而不是 DTO。
      • 业务逻辑: 保留所有的业务规则、验证和状态管理逻辑。
      • 依赖: 这些方法将直接调用 planRepoanalysisPlanTaskManager
      • 日志: 日志记录保持不变,但可能需要调整日志信息以反映领域层的上下文。
      • 错误处理: 错误处理逻辑保持不变,但现在将返回领域层定义的错误。
      • ContentType 自动判断: CreatePlanUpdatePlan 中的 ContentType 自动判断逻辑应该保留在领域层。
      • 执行计数器重置: UpdatePlanStartPlan 中的执行计数器重置逻辑应该保留在领域层。
      • 系统计划限制: 对系统计划的修改、删除、启动、停止限制逻辑应该保留在领域层。
      • 验证和重排顺序: models.PlanValidateExecutionOrder()ReorderSteps() 方法的调用应该在 CreatePlanUpdatePlan 方法的领域层实现中进行,而不是在 DTO 转换函数中。
  6. 修改 NewPlanService 函数: 接收 repository.PlanRepositoryrepository.DeviceRepository (如果需要) 作为参数,并注入到 planServiceImpl 中。

    func NewPlanService(
    	executionManager ExecutionManager,
    	taskManager AnalysisPlanTaskManager,
    	planRepo repository.PlanRepository, // 新增
    	// deviceRepo repository.DeviceRepository, // 如果需要,新增
    	logger *logs.Logger,
    ) Service {
    	return &planServiceImpl{
    		executionManager: executionManager,
    		taskManager:      taskManager,
    		planRepo:         planRepo, // 注入
    		// deviceRepo:       deviceRepo, // 注入
    		logger:           logger,
    	}
    }
    

第二步:修改 app/service/plan_service.go (应用服务层)

  1. 修改 planService 结构体:

    • 移除 planRepo repository.PlanRepository 字段。
    • analysisPlanTaskManager plan.AnalysisPlanTaskManager 字段替换为 domainPlanService plan.Service
    type planService struct {
    	logger            *logs.Logger
    	// planRepo         repository.PlanRepository // 移除
    	domainPlanService plan.Service // 替换为领域层的服务接口
    	// analysisPlanTaskManager plan.AnalysisPlanTaskManager // 移除,由 domainPlanService 内部持有
    }
    
  2. 修改 NewPlanService 函数:

    • 接收 domainPlanService plan.Service 作为参数。
    • planRepoanalysisPlanTaskManager 的注入替换为 domainPlanService
    func NewPlanService(
    	logger *logs.Logger,
    	// planRepo repository.PlanRepository, // 移除
    	domainPlanService plan.Service, // 接收领域层服务
    	// analysisPlanTaskManager plan.AnalysisPlanTaskManager, // 移除
    ) PlanService {
    	return &planService{
    		logger:            logger,
    		domainPlanService: domainPlanService, // 注入领域层服务
    	}
    }
    
  3. 修改 PlanService 接口:

    • 接口定义保持不变,仍然接收和返回 DTO。
  4. 修改 planService 接口实现:

    • CreatePlan:
      • 接收 dto.CreatePlanRequest
      • 使用 dto.NewPlanFromCreateRequest 将 DTO 转换为 *models.Plan注意:此时 NewPlanFromCreateRequest 不再包含 ValidateExecutionOrder()ReorderSteps() 的调用。
      • 调用 s.domainPlanService.CreatePlan(*models.Plan)
      • 将返回的 *models.Plan 转换为 dto.PlanResponse
    • GetPlanByID:
      • 调用 s.domainPlanService.GetPlanByID(id)
      • 将返回的 *models.Plan 转换为 dto.PlanResponse
    • ListPlans:
      • dto.ListPlansQuery 转换为 repository.ListPlansOptions
      • 调用 s.domainPlanService.ListPlans(...)
      • 将返回的 []models.Plan 转换为 []dto.PlanResponse
    • UpdatePlan:
      • 使用 dto.NewPlanFromUpdateRequestdto.UpdatePlanRequest 转换为 *models.Plan注意:此时 NewPlanFromUpdateRequest 不再包含 ValidateExecutionOrder()ReorderSteps() 的调用。
      • 设置 plan.ID = id
      • 调用 s.domainPlanService.UpdatePlan(*models.Plan)
      • 将返回的 *models.Plan 转换为 dto.PlanResponse
    • DeletePlan:
      • 调用 s.domainPlanService.DeletePlan(id)
    • StartPlan:
      • 调用 s.domainPlanService.StartPlan(id)
    • StopPlan:
      • 调用 s.domainPlanService.StopPlan(id)
    • 错误处理: 应用服务层将捕获领域层返回的错误,并可能将其转换为更适合应用服务层或表示层的错误信息(例如,将领域层的 ErrPlanNotFound 转换为 app/service 层定义的 ErrPlanNotFound)。

第三步:修改 internal/app/dto/plan_converter.go

  1. 移除 NewPlanFromCreateRequestNewPlanFromUpdateRequest 中的领域逻辑:
    • NewPlanFromCreateRequestNewPlanFromUpdateRequest 函数中移除 plan.ValidateExecutionOrder()plan.ReorderSteps() 的调用。这些逻辑应该由领域服务来处理。

第四步:更新 main.go 或其他依赖注入点

  • 调整 NewPlanService 的调用,确保正确注入 domain/plan/Service 的实现。

风险与注意事项:

  • 事务管理: 如果领域层的方法需要事务,确保事务在领域层内部或由应用服务层协调。
  • 错误映射: 仔细处理领域层错误到应用服务层错误的映射,确保对外暴露的错误信息是恰当的。
  • 循环依赖: 确保 domain 层不依赖 app 层,app 层可以依赖 domain 层。
  • 测试: 重构后需要对所有相关功能进行全面的单元测试和集成测试。