9.9 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	
			9.9 KiB
		
	
	
	
	
	
	
	
重构方案:将 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 (领域层)
- 
引入必要的依赖:
repository.PlanRepository:用于与计划数据存储交互。repository.DeviceRepository:如果计划逻辑中需要设备信息。models.Plan:领域实体。errors和gorm.ErrRecordNotFound:用于错误处理。models.PlanTypeSystem,models.PlanStatusEnabled,models.PlanContentTypeSubPlans,models.PlanContentTypeTasks等常量。git.huangwc.com/pig/pig-farm-controller/internal/infra/modelsgit.huangwc.com/pig/pig-farm-controller/internal/infra/repositoryerrorsgorm.io/gorm
 - 
定义领域层错误: 将
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("系统计划不允许停止") ) - 
扩展
plan.Service接口:- 将 
app/service/plan_service.go中PlanService接口的所有方法(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) errorStartPlan(id uint) errorStopPlan(id uint) error
 
 - 将 
 - 
修改
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 } - 添加 
 - 
实现
plan.Service接口中的新方法:- 将 
app/service/plan_service.go中planService的所有业务逻辑方法(CreatePlan,GetPlanByID,ListPlans,UpdatePlan,DeletePlan,StartPlan,StopPlan)的实现,迁移到domain/plan/planServiceImpl中。 - 关键修改点:
- 参数和返回值: 确保这些方法现在接收和返回的是 
*models.Plan或其他领域实体,而不是 DTO。 - 业务逻辑: 保留所有的业务规则、验证和状态管理逻辑。
 - 依赖: 这些方法将直接调用 
planRepo和analysisPlanTaskManager。 - 日志: 日志记录保持不变,但可能需要调整日志信息以反映领域层的上下文。
 - 错误处理: 错误处理逻辑保持不变,但现在将返回领域层定义的错误。
 - ContentType 自动判断: 
CreatePlan和UpdatePlan中的ContentType自动判断逻辑应该保留在领域层。 - 执行计数器重置: 
UpdatePlan和StartPlan中的执行计数器重置逻辑应该保留在领域层。 - 系统计划限制: 对系统计划的修改、删除、启动、停止限制逻辑应该保留在领域层。
 - 验证和重排顺序: 
models.Plan的ValidateExecutionOrder()和ReorderSteps()方法的调用应该在CreatePlan和UpdatePlan方法的领域层实现中进行,而不是在 DTO 转换函数中。 
 - 参数和返回值: 确保这些方法现在接收和返回的是 
 
 - 将 
 - 
修改
NewPlanService函数: 接收repository.PlanRepository和repository.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 (应用服务层)
- 
修改
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 内部持有 } - 移除 
 - 
修改
NewPlanService函数:- 接收 
domainPlanService plan.Service作为参数。 - 将 
planRepo和analysisPlanTaskManager的注入替换为domainPlanService。 
func NewPlanService( logger *logs.Logger, // planRepo repository.PlanRepository, // 移除 domainPlanService plan.Service, // 接收领域层服务 // analysisPlanTaskManager plan.AnalysisPlanTaskManager, // 移除 ) PlanService { return &planService{ logger: logger, domainPlanService: domainPlanService, // 注入领域层服务 } } - 接收 
 - 
修改
PlanService接口:- 接口定义保持不变,仍然接收和返回 DTO。
 
 - 
修改
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.NewPlanFromUpdateRequest将dto.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
- 移除 
NewPlanFromCreateRequest和NewPlanFromUpdateRequest中的领域逻辑:- 从 
NewPlanFromCreateRequest和NewPlanFromUpdateRequest函数中移除plan.ValidateExecutionOrder()和plan.ReorderSteps()的调用。这些逻辑应该由领域服务来处理。 
 - 从 
 
第四步:更新 main.go 或其他依赖注入点
- 调整 
NewPlanService的调用,确保正确注入domain/plan/Service的实现。 
风险与注意事项:
- 事务管理: 如果领域层的方法需要事务,确保事务在领域层内部或由应用服务层协调。
 - 错误映射: 仔细处理领域层错误到应用服务层错误的映射,确保对外暴露的错误信息是恰当的。
 - 循环依赖: 确保 
domain层不依赖app层,app层可以依赖domain层。 - 测试: 重构后需要对所有相关功能进行全面的单元测试和集成测试。