# WCS系统渐进式DDD重构设计文档 **创建日期**: 2026-03-11 **版本**: 1.0 **范围**: 全系统重构(渐进式DDD) ## 1. 架构总览 基于您的需求,我将提出一个适合2周时间窗口的渐进式DDD重构方案。 ``` ┌─────────────────────────────────────────────────────────────┐ │ 表现层 (API) │ │ WIDESEAWCS_Server (ASP.NET Core Controllers) │ └─────────────────────────┬───────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 应用层 │ │ 应用服务 / DTO / 命令 / 查询 │ └─────────────────────────┬───────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 领域层 (新增) │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ 设备管理领域 │ │ │ │ • 聚合根: 设备, 设备组, 设备任务 │ │ │ │ • 实体: 设备状态, 设备位置, 设备属性 │ │ │ │ • 值对象: 设备ID, 地址, 状态枚举 │ │ │ │ • 领域服务: 设备调度器, 状态机 │ │ │ │ • 仓储接口: IDeviceRepository, IDeviceTaskRepository │ │ │ └───────────────────────────────────────────────────────┘ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ 仓储作业领域 │ │ │ │ • 聚合根: 作业, 托盘, 库位 │ │ │ │ • 领域事件: 作业完成, 作业异常 │ │ │ └───────────────────────────────────────────────────────┘ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ 系统集成领域 │ │ │ │ • 集成服务: WMS集成, MES集成, ERP集成 │ │ │ └───────────────────────────────────────────────────────┘ │ └─────────────────────────┬───────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 基础设施层 │ │ • 仓储实现 (SqlSugar ORM) │ │ • 通信驱动 (HslCommunication) │ │ • 缓存服务 (Redis) │ │ • 任务调度 (Quartz.NET) │ │ • 数据库访问 (SqlSugar) │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 2. 领域层设计 - 设备管理领域(核心) ### 2.1 聚合根设计 ```csharp // 设备聚合根 public class Device : AggregateRoot { // 基本属性 private DeviceId _id; private DeviceName _name; private DeviceType _type; private DeviceStatus _status; private DeviceAddress _address; private List _properties; // 状态管理 private DateTime _lastConnectedAt; private DateTime _lastHeartbeatAt; private string _errorMessage; // 领域事件 private List _domainEvents = new(); // 公共属性访问器 public DeviceId Id => _id; public DeviceName Name => _name; public DeviceType Type => _type; public DeviceStatus Status => _status; public DeviceAddress Address => _address; public DateTime LastConnectedAt => _lastConnectedAt; public DateTime LastHeartbeatAt => _lastHeartbeatAt; // 行为方法 public void Connect() { if (_status == DeviceStatus.Connected) throw new DomainException("设备已连接"); _status = DeviceStatus.Connected; _lastConnectedAt = DateTime.UtcNow; _domainEvents.Add(new DeviceConnectedEvent(_id)); } public void Disconnect(string reason) { _status = DeviceStatus.Disconnected; _errorMessage = reason; _domainEvents.Add(new DeviceDisconnectedEvent(_id, reason)); } public void UpdateHeartbeat() { _lastHeartbeatAt = DateTime.UtcNow; _domainEvents.Add(new DeviceHeartbeatEvent(_id)); } public void SetProperty(string key, string value) { var property = _properties.FirstOrDefault(p => p.Key == key); if (property != null) property.UpdateValue(value); else _properties.Add(DeviceProperty.Create(key, value)); } // 状态设置方法(供状态机使用) internal void SetStatus(DeviceStatus status) { _status = status; } public IReadOnlyCollection GetDomainEvents() => _domainEvents.AsReadOnly(); public void ClearDomainEvents() => _domainEvents.Clear(); } // 设备组聚合根 public class DeviceGroup : AggregateRoot { private DeviceGroupId _id; private DeviceGroupName _name; private List _deviceIds; private GroupStrategy _strategy; private int _currentIndex; private static readonly Random _random = Random.Shared; // 设备组聚合根(继续) public void AddDevice(DeviceId deviceId) { if (_deviceIds.Contains(deviceId)) throw new DomainException("设备已在组内"); _deviceIds.Add(deviceId); } public void RemoveDevice(DeviceId deviceId) { _deviceIds.Remove(deviceId); } // 轮询策略 public DeviceId GetNextDevice() { if (!_deviceIds.Any()) throw new DomainException("设备组为空"); switch (_strategy) { case GroupStrategy.RoundRobin: return _deviceIds[_currentIndex++ % _deviceIds.Count]; case GroupStrategy.Random: return _deviceIds[_random.Next(_deviceIds.Count)]; default: return _deviceIds[0]; } } } // 设备任务聚合根 public class DeviceTask : AggregateRoot { private DeviceTaskId _id; private DeviceId _deviceId; private TaskType _type; private TaskStatus _status; private TaskPriority _priority; private TaskPayload _payload; private DateTime _createdAt; private DateTime? _startedAt; private DateTime? _completedAt; private string? _errorMessage; private List _domainEvents = new(); public void Start() { if (_status != TaskStatus.Pending) throw new DomainException("任务状态不正确"); _status = TaskStatus.Running; _startedAt = DateTime.UtcNow; _domainEvents.Add(new TaskStartedEvent(_id, _deviceId)); } public void Complete() { if (_status != TaskStatus.Running) throw new DomainException("任务未在运行中"); _status = TaskStatus.Completed; _completedAt = DateTime.UtcNow; _domainEvents.Add(new TaskCompletedEvent(_id, _deviceId)); } public void Fail(string errorMessage) { _status = TaskStatus.Failed; _errorMessage = errorMessage; _completedAt = DateTime.UtcNow; _domainEvents.Add(new TaskFailedEvent(_id, _deviceId, errorMessage)); } public IReadOnlyCollection GetDomainEvents() => _domainEvents.AsReadOnly(); public void ClearDomainEvents() => _domainEvents.Clear(); } ``` ### 2.2 值对象设计 ```csharp // 设备ID值对象 public record DeviceId(Guid Value) { public static DeviceId New() => new DeviceId(Guid.NewGuid()); public static DeviceId From(Guid value) => new DeviceId(value); } // 设备名称值对象 public record DeviceName(string Value) { public DeviceName(string value) { if (string.IsNullOrWhiteSpace(value) || value.Length > 100) throw new ArgumentException("设备名称无效"); Value = value; } } // 设备类型枚举 public enum DeviceType { StackerCrane, // 堆垛机 ConveyorLine, // 输送线 ShuttleCar, // 穿梭车 Robot, // 机械手 AGV // 自动导引车 } // 设备状态枚举 public enum DeviceStatus { Disconnected, // 未连接 Connecting, // 连接中 Connected, // 已连接 Busy, // 忙碌 Error, // 错误 Maintenance // 维护中 } // 设备地址值对象 public record DeviceAddress(string Ip, int Port) { public DeviceAddress(string ip, int port) { if (!IPAddress.TryParse(ip, out _)) throw new ArgumentException("IP地址无效"); if (port < 1 || port > 65535) throw new ArgumentException("端口无效"); Ip = ip; Port = port; } } ``` ### 2.3 领域服务设计 ```csharp // 设备调度领域服务 public interface IDeviceScheduler { Task CreateTask(DeviceId deviceId, TaskType type, TaskPayload payload); Task AssignTask(DeviceId deviceId); Task CompleteTask(DeviceTaskId taskId); Task FailTask(DeviceTaskId taskId, string errorMessage); } public class DeviceScheduler : IDeviceScheduler { private readonly IDeviceTaskRepository _taskRepository; private readonly IDeviceRepository _deviceRepository; private readonly ITaskQueue _taskQueue; public async Task CreateTask(DeviceId deviceId, TaskType type, TaskPayload payload) { var device = await _deviceRepository.GetById(deviceId); if (device == null) throw new DomainException("设备不存在"); if (device.Status != DeviceStatus.Connected) throw new DomainException("设备未连接"); var task = DeviceTask.Create(deviceId, type, payload); await _taskRepository.Add(task); await _taskQueue.Enqueue(task); return task; } public async Task AssignTask(DeviceId deviceId) { var tasks = await _taskRepository.GetPendingTasksForDevice(deviceId); if (!tasks.Any()) return null; var task = tasks.OrderBy(t => t.Priority).First(); task.Start(); await _taskRepository.Update(task); return task; } } // 设备状态机领域服务 public interface IDeviceStateMachine { bool CanTransition(DeviceStatus current, DeviceStatus target); void Transition(Device device, DeviceStatus target); } public class DeviceStateMachine : IDeviceStateMachine { private static readonly Dictionary> _transitions = new() { [DeviceStatus.Disconnected] = new() { DeviceStatus.Connecting, DeviceStatus.Maintenance }, [DeviceStatus.Connecting] = new() { DeviceStatus.Connected, DeviceStatus.Error }, [DeviceStatus.Connected] = new() { DeviceStatus.Busy, DeviceStatus.Disconnected, DeviceStatus.Error, DeviceStatus.Maintenance }, [DeviceStatus.Busy] = new() { DeviceStatus.Connected, DeviceStatus.Error }, [DeviceStatus.Error] = new() { DeviceStatus.Disconnected, DeviceStatus.Maintenance }, [DeviceStatus.Maintenance] = new() { DeviceStatus.Disconnected } }; public bool CanTransition(DeviceStatus current, DeviceStatus target) { return _transitions.TryGetValue(current, out var allowed) && allowed.Contains(target); } public void Transition(Device device, DeviceStatus target) { if (!CanTransition(device.Status, target)) throw new DomainException($"无效的状态转换: {device.Status} -> {target}"); device.SetStatus(target); } } ``` ### 2.4 基础设施类定义 ```csharp // 聚合根基类 public abstract class AggregateRoot { public TId Id { get; protected set; } public int Version { get; private set; } private readonly List _domainEvents = new(); protected AggregateRoot() { } protected AggregateRoot(TId id) { Id = id; } public IReadOnlyCollection GetDomainEvents() => _domainEvents.AsReadOnly(); public void ClearDomainEvents() => _domainEvents.Clear(); protected void AddDomainEvent(IDomainEvent domainEvent) { _domainEvents.Add(domainEvent); } } // 领域事件接口 public interface IDomainEvent { DateTime OccurredOn { get; } } // 领域事件基类 public abstract record DomainEvent : IDomainEvent { public DateTime OccurredOn { get; init; } = DateTime.UtcNow; } // 领域事件调度器接口 public interface IDomainEventDispatcher { Task Dispatch(IDomainEvent domainEvent); Task DispatchAsync(IEnumerable domainEvents); } // 任务队列接口 public interface ITaskQueue { Task Enqueue(T item) where T : class; Task Dequeue() where T : class; Task Count(); } // 仓储基接口 public interface IRepository where TEntity : AggregateRoot { Task GetById(TId id); Task Add(TEntity entity); Task Update(TEntity entity); Task Delete(TId id); } // 工作单元接口 public interface IUnitOfWork : IDisposable { ITransaction BeginTransaction(); Task Commit(); Task Rollback(); } public interface ITransaction : IDisposable { Task Commit(); Task Rollback(); } ``` ### 2.5 仓储接口设计 ```csharp // 设备仓储接口 public interface IDeviceRepository : IRepository { Task GetByName(DeviceName name); Task> GetByType(DeviceType type); Task> GetByStatus(DeviceStatus status); Task> GetAllConnected(); } // 设备任务仓储接口 public interface IDeviceTaskRepository : IRepository { Task> GetPendingTasksForDevice(DeviceId deviceId); Task> GetRunningTasksForDevice(DeviceId deviceId); Task> GetTasksByStatus(TaskStatus status); } ``` --- ## 3. 应用层设计 ### 3.1 应用服务设计 ```csharp // 设备应用服务 public class DeviceApplicationService { private readonly IDeviceRepository _deviceRepository; private readonly IDeviceScheduler _deviceScheduler; private readonly IUnitOfWork _unitOfWork; public async Task GetDevice(DeviceId id) { var device = await _deviceRepository.GetById(id); return device.ToDto(); } public async Task CreateTask(CreateDeviceTaskCommand command) { await using var transaction = await _unitOfWork.BeginTransaction(); var deviceId = DeviceId.From(command.DeviceId); var task = await _deviceScheduler.CreateTask( deviceId, command.Type, new TaskPayload(command.Payload) ); await _unitOfWork.Commit(); return task.ToDto(); } public async Task CompleteTask(CompleteDeviceTaskCommand command) { var taskId = DeviceTaskId.From(command.TaskId); await _deviceScheduler.CompleteTask(taskId); } } // 命令和查询对象 public record CreateDeviceTaskCommand(Guid DeviceId, TaskType Type, string Payload); public record CompleteDeviceTaskCommand(Guid TaskId); // DTO public record DeviceDto(Guid Id, string Name, DeviceType Type, DeviceStatus Status); public record DeviceTaskDto(Guid Id, Guid DeviceId, TaskType Type, TaskStatus Status); ``` --- ## 4. 基础设施层重构 ### 4.1 仓储实现 ```csharp // 设备仓储实现 public class DeviceRepository : IDeviceRepository { private readonly ISqlSugarClient _db; private readonly ICacheService _cache; public DeviceRepository(ISqlSugarClient db, ICacheService cache) { _db = db; _cache = cache; } public async Task GetById(DeviceId id) { // 先查缓存 var cached = await _cache.Get($"device:{id.Value}"); if (cached != null) return cached; // 查数据库 var entity = await _db.Queryable() .Where(d => d.DeviceId == id.Value) .FirstAsync(); if (entity == null) return null; // 转换为领域模型 var device = MapToDomain(entity); // 写入缓存 await _cache.Set($"device:{id.Value}", device, TimeSpan.FromMinutes(5)); return device; } public async Task Add(Device device) { var entity = MapToEntity(device); await _db.Insertable(entity).ExecuteCommandAsync(); // 清除相关缓存 await _cache.RemoveByPrefix("device:"); } private Device MapToDomain(Dt_DeviceInfo entity) { /* ... */ } private Dt_DeviceInfo MapToEntity(Device domain) { /* ... */ } } ``` ### 4.2 通信驱动封装 ```csharp // 设备通信适配器接口 public interface IDeviceCommunicatorAdapter { Task Connect(DeviceAddress address); Task Disconnect(); Task ReadData(string address); Task WriteData(string address, object value); Task IsConnected(); } // Siemens PLC适配器 public class SiemensCommunicatorAdapter : IDeviceCommunicatorAdapter { private readonly SiemensS7Net _plc; public SiemensCommunicatorAdapter(DeviceAddress address) { _plc = new SiemensS7Net(SiemensPLCS.S1200, address.Ip); _plc.ConnectTimeOut = 5000; } public async Task Connect(DeviceAddress address) { var result = await Task.Run(() => _plc.ConnectServer()); return result.IsSuccess; } public async Task ReadData(string address) { var result = await Task.Run(() => _plc.Read(address)); if (!result.IsSuccess) throw new CommunicationException($"读取失败: {result.Message}"); return new DeviceData(result.Content); } // ... 其他方法 } ``` --- ## 5. 分层清晰化 ### 5.1 项目结构重组 ``` WIDESEAWCS_Domain/ ├── DeviceManagement/ │ ├── Aggregates/ │ │ ├── Device.cs │ │ ├── DeviceGroup.cs │ │ └── DeviceTask.cs │ ├── Entities/ │ │ ├── DeviceStatus.cs │ │ ├── DeviceLocation.cs │ │ └── DeviceProperty.cs │ ├── ValueObjects/ │ │ ├── DeviceId.cs │ │ ├── DeviceName.cs │ │ ├── DeviceAddress.cs │ │ └── TaskPayload.cs │ ├── Services/ │ │ ├── IDeviceScheduler.cs │ │ ├── DeviceScheduler.cs │ │ ├── IDeviceStateMachine.cs │ │ └── DeviceStateMachine.cs │ ├── Repositories/ │ │ ├── IDeviceRepository.cs │ │ └── IDeviceTaskRepository.cs │ └── Events/ │ ├── DeviceConnectedEvent.cs │ └── TaskCompletedEvent.cs └── WarehouseOperations/ └── ... WIDESEAWCS_Application/ ├── Services/ │ └── DeviceApplicationService.cs ├── Commands/ │ └── CreateDeviceTaskCommand.cs ├── Queries/ │ └── GetDeviceQuery.cs └── DTOs/ └── DeviceDto.cs WIDESEAWCS_Infrastructure/ ├── Persistence/ │ ├── Repositories/ │ │ ├── DeviceRepository.cs │ │ └── DeviceTaskRepository.cs │ └── SqlSugar/ │ └── DbContext.cs ├── Communication/ │ ├── Adapters/ │ │ ├── SiemensCommunicatorAdapter.cs │ │ ├── OmronCommunicatorAdapter.cs │ │ └── ModbusCommunicatorAdapter.cs │ └── IDeviceCommunicatorAdapter.cs ├── Caching/ │ └── RedisCacheService.cs └── Scheduling/ └── QuartzJobManager.cs WIDESEAWCS_Server/ └── Controllers/ └── DeviceController.cs ``` --- ## 6. 性能优化策略 ### 6.1 缓存优化 ```csharp // 多级缓存策略 public class HybridCacheService : ICacheService { private readonly IMemoryCache _l1Cache; private readonly IConnectionMultiplexer _redis; private readonly IDistributedCache _l2Cache; public async Task Get(string key) { // L1缓存 if (_l1Cache.TryGetValue(key, out T l1Value)) return l1Value; // L2缓存 var l2Value = await _l2Cache.GetStringAsync(key); if (l2Value != null) { var value = JsonSerializer.Deserialize(l2Value); _l1Cache.Set(key, value, TimeSpan.FromMinutes(1)); return value; } return default; } public async Task Set(string key, T value, TimeSpan? expiry = null) { _l1Cache.Set(key, value, TimeSpan.FromMinutes(1)); var serialized = JsonSerializer.Serialize(value); await _l2Cache.SetStringAsync(key, serialized, expiry); } } ``` ### 6.2 数据库查询优化 ```csharp // 批量查询优化 public class DeviceRepository : IDeviceRepository { public async Task> GetByIds(IEnumerable ids) { var idList = ids.Select(id => id.Value).ToList(); // 批量查询 var entities = await _db.Queryable() .In(idList) .ToListAsync(); return entities.ToDictionary( e => DeviceId.From(e.DeviceId), e => MapToDomain(e) ); } // 延迟查询 public async Task> QueryActiveDevices() { return await _db.Queryable() .Where(d => d.Status != DeviceStatus.Disconnected) .OrderBy(d => d.Name) .Select(d => MapToDomain(d)) .ToListAsync(); } } ``` --- ## 7. 错误处理和日志统一 ### 7.1 统一异常处理 ```csharp // 领域异常基类 public abstract class DomainException : Exception { public string ErrorCode { get; } protected DomainException(string message, string errorCode = "DOMAIN_ERROR") : base(message) { ErrorCode = errorCode; } } // 应用异常 public class ApplicationException : Exception { public string ErrorCode { get; } public ApplicationException(string message, string errorCode = "APP_ERROR") : base(message) { ErrorCode = errorCode; } } // 全局异常处理中间件 public class GlobalExceptionHandlerMiddleware { public async Task InvokeAsync(HttpContext context, RequestDelegate next) { try { await next(context); } catch (DomainException ex) { context.Response.StatusCode = 400; await context.Response.WriteAsJsonAsync(new { error = ex.ErrorCode, message = ex.Message }); } catch (ApplicationException ex) { context.Response.StatusCode = 500; await context.Response.WriteAsJsonAsync(new { error = ex.ErrorCode, message = ex.Message }); } } } ``` ### 7.2 结构化日志 ```csharp // 领域事件日志 public class DomainEventLogger : IDomainEventDispatcher { private readonly ILogger _logger; public async Task Dispatch(IDomainEvent domainEvent) { _logger.LogInformation("领域事件: {EventType}, 数据: {EventData}", domainEvent.GetType().Name, JsonSerializer.Serialize(domainEvent)); // 发送到事件总线 } } // 设备操作日志 public class DeviceOperationLogger : IDeviceOperationLogger { public void LogConnect(DeviceId deviceId, bool success, string? error = null) { if (success) { _logger.LogInformation("设备连接成功: {DeviceId}", deviceId.Value); } else { _logger.LogError("设备连接失败: {DeviceId}, 错误: {Error}", deviceId.Value, error); } } } ``` --- ## 8. 实施计划(2周) ### 第1周:领域层搭建 | 天数 | 任务 | |------|------| | Day 1-2 | 创建领域项目结构,定义值对象和聚合根 | | Day 3-4 | 实现领域服务和状态机 | | Day 5 | 创建仓储接口,定义领域事件 | ### 第2周:应用层和基础设施层 | 天数 | 任务 | |------|------| | Day 1-2 | 实现应用服务和DTO | | Day 3 | 创建仓储实现,迁移数据访问逻辑 | | Day 4 | 封装通信驱动,优化缓存策略 | | Day 5 | 统一错误处理和日志,集成测试 | --- ## 9. 设计决策记录 ### 9.1 为什么选择渐进式DDD而非完整DDD重构? **决策**:选择渐进式DDD重构而非完整DDD重构 **理由**: 1. 时间约束:2周内无法完成完整DDD重构 2. 风险控制:渐进式降低风险,可分阶段验证 3. 系统稳定性:保持现有功能可用 4. 资源效率:优先重构核心领域 ### 9.2 为什么将设备管理作为核心领域? **决策**:将设备管理作为第一个重构的核心领域 **理由**: 1. 业务优先级:设备管理是WCS系统的核心 2. 代码复杂度:设备管理包含状态、调度、通信等复杂逻辑 3. 优化空间大:设备管理存在较多性能和耦合问题 4. 影响范围广:设备管理是其他业务的基础 ### 9.3 为什么采用CQRS模式? **决策**:在应用层采用CQRS模式 **理由**: 1. 关注点分离:命令和查询职责清晰 2. 性能优化:可分别优化读写操作 3. 扩展性好:便于添加缓存、事件处理 --- ## 10. 非功能性需求 ### 10.1 性能要求 - 设备状态查询响应时间 < 100ms - 任务创建响应时间 < 200ms - 支持至少100个并发设备连接 ### 10.2 可维护性要求 - 代码覆盖率 > 80% - 圈复杂度 < 10 - 遵循单一职责原则 ### 10.3 可扩展性要求 - 支持新增设备类型无需修改核心代码 - 支持新增任务类型 - 支持多种通信协议扩展 --- ## 11. 风险评估 | 风险 | 可能性 | 影响 | 缓解措施 | |------|--------|------|----------| | 时间不足 | 高 | 高 | 分阶段实施,优先核心功能 | | 数据迁移问题 | 中 | 高 | 充分测试,保留回滚方案 | | 性能下降 | 中 | 中 | 性能基准测试,监控优化 | | 团队适应 | 低 | 中 | 提供培训,文档完善 |