创建日期: 2026-03-11
版本: 1.0
范围: 全系统重构(渐进式DDD)
基于您的需求,我将提出一个适合2周时间窗口的渐进式DDD重构方案。
┌─────────────────────────────────────────────────────────────┐
│ 表现层 (API) │
│ WIDESEAWCS_Server (ASP.NET Core Controllers) │
└─────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 应用层 │
│ 应用服务 / DTO / 命令 / 查询 │
└─────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 领域层 (新增) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 设备管理领域 │ │
│ │ • 聚合根: 设备, 设备组, 设备任务 │ │
│ │ • 实体: 设备状态, 设备位置, 设备属性 │ │
│ │ • 值对象: 设备ID, 地址, 状态枚举 │ │
│ │ • 领域服务: 设备调度器, 状态机 │ │
│ │ • 仓储接口: IDeviceRepository, IDeviceTaskRepository │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 仓储作业领域 │ │
│ │ • 聚合根: 作业, 托盘, 库位 │ │
│ │ • 领域事件: 作业完成, 作业异常 │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 系统集成领域 │ │
│ │ • 集成服务: WMS集成, MES集成, ERP集成 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 基础设施层 │
│ • 仓储实现 (SqlSugar ORM) │
│ • 通信驱动 (HslCommunication) │
│ • 缓存服务 (Redis) │
│ • 任务调度 (Quartz.NET) │
│ • 数据库访问 (SqlSugar) │
└─────────────────────────────────────────────────────────────┘
// 设备聚合根
public class Device : AggregateRoot<DeviceId>
{
// 基本属性
private DeviceId _id;
private DeviceName _name;
private DeviceType _type;
private DeviceStatus _status;
private DeviceAddress _address;
private List<DeviceProperty> _properties;
// 状态管理
private DateTime _lastConnectedAt;
private DateTime _lastHeartbeatAt;
private string _errorMessage;
// 领域事件
private List<IDomainEvent> _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<IDomainEvent> GetDomainEvents() => _domainEvents.AsReadOnly();
public void ClearDomainEvents() => _domainEvents.Clear();
}
// 设备组聚合根
public class DeviceGroup : AggregateRoot<DeviceGroupId>
{
private DeviceGroupId _id;
private DeviceGroupName _name;
private List<DeviceId> _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<DeviceTaskId>
{
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<IDomainEvent> _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<IDomainEvent> GetDomainEvents() => _domainEvents.AsReadOnly();
public void ClearDomainEvents() => _domainEvents.Clear();
}
// 设备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;
}
}
// 设备调度领域服务
public interface IDeviceScheduler
{
Task<DeviceTask> CreateTask(DeviceId deviceId, TaskType type, TaskPayload payload);
Task<DeviceTask> 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<DeviceTask> 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<DeviceTask> 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<DeviceStatus, HashSet<DeviceStatus>> _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);
}
}
// 聚合根基类
public abstract class AggregateRoot<TId>
{
public TId Id { get; protected set; }
public int Version { get; private set; }
private readonly List<IDomainEvent> _domainEvents = new();
protected AggregateRoot() { }
protected AggregateRoot(TId id)
{
Id = id;
}
public IReadOnlyCollection<IDomainEvent> 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<IDomainEvent> domainEvents);
}
// 任务队列接口
public interface ITaskQueue
{
Task Enqueue<T>(T item) where T : class;
Task<T> Dequeue<T>() where T : class;
Task<int> Count();
}
// 仓储基接口
public interface IRepository<TEntity, TId> where TEntity : AggregateRoot<TId>
{
Task<TEntity?> 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();
}
// 设备仓储接口
public interface IDeviceRepository : IRepository<Device, DeviceId>
{
Task<Device?> GetByName(DeviceName name);
Task<IEnumerable<Device>> GetByType(DeviceType type);
Task<IEnumerable<Device>> GetByStatus(DeviceStatus status);
Task<IEnumerable<Device>> GetAllConnected();
}
// 设备任务仓储接口
public interface IDeviceTaskRepository : IRepository<DeviceTask, DeviceTaskId>
{
Task<IEnumerable<DeviceTask>> GetPendingTasksForDevice(DeviceId deviceId);
Task<IEnumerable<DeviceTask>> GetRunningTasksForDevice(DeviceId deviceId);
Task<IEnumerable<DeviceTask>> GetTasksByStatus(TaskStatus status);
}
// 设备应用服务
public class DeviceApplicationService
{
private readonly IDeviceRepository _deviceRepository;
private readonly IDeviceScheduler _deviceScheduler;
private readonly IUnitOfWork _unitOfWork;
public async Task<DeviceDto> GetDevice(DeviceId id)
{
var device = await _deviceRepository.GetById(id);
return device.ToDto();
}
public async Task<DeviceTaskDto> 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);
// 设备仓储实现
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<Device?> GetById(DeviceId id)
{
// 先查缓存
var cached = await _cache.Get<Device>($"device:{id.Value}");
if (cached != null)
return cached;
// 查数据库
var entity = await _db.Queryable<Dt_DeviceInfo>()
.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) { /* ... */ }
}
// 设备通信适配器接口
public interface IDeviceCommunicatorAdapter
{
Task<bool> Connect(DeviceAddress address);
Task<bool> Disconnect();
Task<DeviceData> ReadData(string address);
Task WriteData(string address, object value);
Task<bool> 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<bool> Connect(DeviceAddress address)
{
var result = await Task.Run(() => _plc.ConnectServer());
return result.IsSuccess;
}
public async Task<DeviceData> ReadData(string address)
{
var result = await Task.Run(() => _plc.Read(address));
if (!result.IsSuccess)
throw new CommunicationException($"读取失败: {result.Message}");
return new DeviceData(result.Content);
}
// ... 其他方法
}
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
// 多级缓存策略
public class HybridCacheService : ICacheService
{
private readonly IMemoryCache _l1Cache;
private readonly IConnectionMultiplexer _redis;
private readonly IDistributedCache _l2Cache;
public async Task<T?> Get<T>(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<T>(l2Value);
_l1Cache.Set(key, value, TimeSpan.FromMinutes(1));
return value;
}
return default;
}
public async Task Set<T>(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);
}
}
// 批量查询优化
public class DeviceRepository : IDeviceRepository
{
public async Task<IDictionary<DeviceId, Device>> GetByIds(IEnumerable<DeviceId> ids)
{
var idList = ids.Select(id => id.Value).ToList();
// 批量查询
var entities = await _db.Queryable<Dt_DeviceInfo>()
.In(idList)
.ToListAsync();
return entities.ToDictionary(
e => DeviceId.From(e.DeviceId),
e => MapToDomain(e)
);
}
// 延迟查询
public async Task<IEnumerable<Device>> QueryActiveDevices()
{
return await _db.Queryable<Dt_DeviceInfo>()
.Where(d => d.Status != DeviceStatus.Disconnected)
.OrderBy(d => d.Name)
.Select(d => MapToDomain(d))
.ToListAsync();
}
}
// 领域异常基类
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
});
}
}
}
// 领域事件日志
public class DomainEventLogger : IDomainEventDispatcher
{
private readonly ILogger<DomainEventLogger> _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);
}
}
}
| 天数 | 任务 |
|---|---|
| Day 1-2 | 创建领域项目结构,定义值对象和聚合根 |
| Day 3-4 | 实现领域服务和状态机 |
| Day 5 | 创建仓储接口,定义领域事件 |
| 天数 | 任务 |
|---|---|
| Day 1-2 | 实现应用服务和DTO |
| Day 3 | 创建仓储实现,迁移数据访问逻辑 |
| Day 4 | 封装通信驱动,优化缓存策略 |
| Day 5 | 统一错误处理和日志,集成测试 |
决策:选择渐进式DDD重构而非完整DDD重构
理由:
1. 时间约束:2周内无法完成完整DDD重构
2. 风险控制:渐进式降低风险,可分阶段验证
3. 系统稳定性:保持现有功能可用
4. 资源效率:优先重构核心领域
决策:将设备管理作为第一个重构的核心领域
理由:
1. 业务优先级:设备管理是WCS系统的核心
2. 代码复杂度:设备管理包含状态、调度、通信等复杂逻辑
3. 优化空间大:设备管理存在较多性能和耦合问题
4. 影响范围广:设备管理是其他业务的基础
决策:在应用层采用CQRS模式
理由:
1. 关注点分离:命令和查询职责清晰
2. 性能优化:可分别优化读写操作
3. 扩展性好:便于添加缓存、事件处理
| 风险 | 可能性 | 影响 | 缓解措施 |
|---|---|---|---|
| 时间不足 | 高 | 高 | 分阶段实施,优先核心功能 |
| 数据迁移问题 | 中 | 高 | 充分测试,保留回滚方案 |
| 性能下降 | 中 | 中 | 性能基准测试,监控优化 |
| 团队适应 | 低 | 中 | 提供培训,文档完善 |