7.6 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	方案:维护设备与任务的关联关系
1. 目标
在对计划(Plan)及其包含的任务(Task)进行创建、更新、删除(CRUD)操作时,同步维护 device_tasks 这张多对多关联表。
这是实现“删除设备前检查其是否被任务使用”这一需求的基础。
2. 核心挑战
- 参数结构异构性:不同类型的任务(
TaskType),其设备 ID 存储在Parameters(JSON) 字段中的key和数据结构(单个 ID 或 ID 数组)各不相同。 - 分层架构原则:解析 
Parameters以提取设备 ID 的逻辑属于 业务规则,需要找到一个合适的位置来封装它,以维持各层职责的清晰。 
3. 方案设计
本方案旨在最大化地复用现有领域模型和逻辑,通过扩展 TaskFactory 来实现设备ID的解析,从而保持了各领域模块的高内聚和低耦合。
3.1. 核心思路:复用领域对象与工厂
我们不移动任何结构体,也不在 plan 包中引入任何具体任务的实现细节。取而代之,我们利用现有的 TaskFactory
和各个任务领域对象自身的能力来解析参数。
每个具体的任务领域对象(如 ReleaseFeedWeightTask)最了解如何解析自己的 Parameters。因此,我们将解析设备ID的责任完全交还给它们。
3.2. 扩展 TaskFactory
- 动作:在 
plan.TaskFactory接口中增加一个新方法CreateTaskFromModel(*models.Task) (TaskDeviceIDResolver, error)。 - 目的:此方法允许我们在非任务执行的场景下(例如,在增删改查计划时),仅根据数据库模型 
models.Task来创建一个临时的、轻量级的任务领域对象。 - 实现:在 
internal/domain/task/task.go的taskFactory中实现此方法。它会根据传入的taskModel.Type,switch-case来调用相应的构造函数(如NewReleaseFeedWeightTask)创建实例。 - 实现:
 - 优势:
- 高内聚,低耦合:
plan包保持通用,无需了解任何具体任务的参数细节。参数定义和解析逻辑都保留在各自的task包内。 - 逻辑复用:完美复用了您已在 
ReleaseFeedWeightTask中实现的ResolveDeviceIDs方法,避免了重复代码。 
 - 高内聚,低耦合:
 
3.3. 调整领域服务层 (PlanService)
PlanService 将作为此业务用例的核心编排者。借助 UnitOfWork 模式,它可以在单个事务中协调多个仓库,完成数据准备和持久化。
- 职责:在创建或更新计划的业务流程中,负责解析任务参数、准备设备关联数据,并调用仓库层完成持久化。
 - 实现:
- 向 
planServiceImpl注入repository.UnitOfWork和plan.TaskFactory。 - 在 
CreatePlan和UpdatePlan方法中,使用unitOfWork.ExecuteInTransaction来包裹整个操作。 - 在事务闭包内,遍历计划中的所有任务 (
models.Task):- 调用 
taskFactory.CreateTaskFromModel(taskModel)创建一个临时的任务领域对象。 - 调用该领域对象的 
ResolveDeviceIDs()方法获取设备ID列表。 - 使用事务性的 
DeviceRepository查询出设备实体。 - 将查询到的设备实体列表填充到 
taskModel.Devices字段中。 
 - 调用 
 - 最后,将填充好关联数据的 
plan对象传递给事务性的PlanRepository进行创建或更新。 
 - 向 
 - 优势:
- 职责清晰:
PlanService完整地拥有了“创建/更新计划”的业务逻辑,而仓库层则回归到纯粹的数据访问职责。 - 数据一致性:
UnitOfWork确保了从准备数据(查询设备)到最终持久化(创建计划和关联)的所有数据库操作都在一个原子事务中完成。 
 - 职责清晰:
 
3.4. 调整仓库层 (PlanRepository)
仓库层被简化,回归其作为数据持久化网关的纯粹角色。
- 职责:负责 
Plan及其直接子对象(Task,SubPlan)的 CRUD 操作。 - 实现:
CreatePlan和UpdatePlanMetadataAndStructure方法将被简化。它们不再需要任何特殊的关联处理逻辑(如Association().Replace())。- 只需接收一个由 
PlanService准备好的、task.Devices字段已被填充的plan对象。 - 在 
CreatePlan中,调用tx.Create(plan)时,GORM 会自动级联创建Plan、Task以及device_tasks中的关联记录。 - 在 
UpdatePlanMetadataAndStructure的reconcileTasks逻辑中,对于新创建的任务,GORM 的tx.Create(task)同样会自动处理其设备关联。 
 
3.5. 整体流程
以 创建计划 为例:
PlanController调用PlanService.CreatePlan(plan)。PlanService调用unitOfWork.ExecuteInTransaction启动一个数据库事务。- 在事务闭包内,
PlanService遍历plan对象中的所有task。 - 对于每一个 
task模型,调用taskFactory.CreateTaskFromModel(task)创建一个临时的领域对象。 - 调用该领域对象的 
ResolveDeviceIDs()方法,获取其使用的设备 ID 列表。 - 如果返回了设备 ID 列表,则使用事务性的 
DeviceRepository查询出[]models.Device实体。 - 所有 
task的关联数据准备好后,调用事务性的PlanRepository.CreatePlan(plan)。GORM 在创建plan和task的同时,会自动创建device_tasks表中的关联记录。 UnitOfWork提交事务。
更新计划 的流程与创建类似,在 UpdatePlanMetadataAndStructure 方法中,由于会先删除旧任务再创建新任务,因此在创建新任务后执行相同的设备关联步骤。
删除计划 时,由于 Task 模型上配置了 OnDelete:CASCADE,GORM 会自动删除关联的 Task 记录。同时,GORM 的多对多删除逻辑会自动清理
device_tasks 表中与被删除任务相关的记录。因此 DeletePlan 方法无需修改。
4. 实施步骤
- 
扩展
TaskFactory接口- 在 
internal/domain/plan/task.go文件中,为TaskFactory接口添加CreateTaskFromModel(*models.Task) (TaskDeviceIDResolver, error)方法。 
 - 在 
 - 
实现
TaskFactory新方法- 在 
internal/domain/task/task.go文件中,为taskFactory结构体实现CreateTaskFromModel方法。 
 - 在 
 - 
修改
PlanService- 在 
internal/domain/plan/plan_service.go中:- 修改 
planServiceImpl结构体,增加unitOfWork repository.UnitOfWork和taskFactory TaskFactory字段。 - 修改 
NewPlanService构造函数,接收并注入这些新依赖。 - 重构 
CreatePlan和UpdatePlan方法,使用UnitOfWork包裹事务,并在其中实现数据准备和关联逻辑。 
 - 修改 
 
 - 在 
 - 
修改
PlanRepository- 在 
internal/infra/repository/plan_repository.go中:- 简化 
CreatePlan和UpdatePlanMetadataAndStructure方法。移除所有手动处理设备关联的代码(例如,如果之前有Association("Devices").Replace()等调用,则应删除)。 - 确保这两个方法的核心逻辑就是调用 GORM 的 
Create或Updates,信任 GORM 会根据传入模型中已填充的Devices字段来自动维护多对多关联。 
 - 简化 
 
 - 在 
 - 
修改依赖注入
- 在 
internal/core/component_initializers.go(或类似的依赖注入入口文件) 中:- 将 
unitOfWork和taskFactory实例传递给plan.NewPlanService的构造函数。 
 - 将 
 
 - 在 
 
5. 结论
此方案通过复用现有的领域对象和工厂模式,优雅地解决了设备关联维护的问题。它保持了清晰的架构分层和模块职责,在实现功能的同时,为项目未来的扩展和维护奠定了坚实、可扩展的基础。