For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (
- [ ]) syntax for tracking.
目标: 实现WCS系统的渐进式DDD重构,包括领域层、应用层和基础设施层,重点重构设备管理领域
架构: 采用标准的四层DDD架构:表现层 → 应用层 → 领域层 → 基础设施层,使用CQRS模式分离命令和查询,领域事件驱动设计
技术栈:
- .NET 6.0
- SqlSugar ORM
- Redis(StackExchange.Redis)
- xUnit + Moq(测试框架)
- Autofac(依赖注入)
Files:
- Create: WIDESEAWCS_Domain/WIDESEAWCS_Domain.csproj
- Create: WIDESEAWCS_Domain/Common/AggregateRoot.cs
- Create: WIDESEAWCS_Domain/Common/IDomainEvent.cs
- Create: WIDESEAWCS_Domain/Common/DomainEvent.cs
- Create: WIDESEAWCS_Domain/Common/DomainException.cs
- Test: WIDESEAWCS_Domain.Tests/AggregateRootTests.cs
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
namespace WIDESEAWCS_Domain.Common;
public abstract class AggregateRoot<TId>
{
public TId Id { get; protected set; } = default!;
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);
}
protected void IncrementVersion()
{
Version++;
}
}
namespace WIDESEAWCS_Domain.Common;
public interface IDomainEvent
{
DateTime OccurredOn { get; }
}
namespace WIDESEAWCS_Domain.Common;
public abstract record DomainEvent : IDomainEvent
{
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
}
namespace WIDESEAWCS_Domain.Common;
public abstract class DomainException : Exception
{
public string ErrorCode { get; }
protected DomainException(string message, string errorCode = "DOMAIN_ERROR")
: base(message)
{
ErrorCode = errorCode;
}
}
public class InvalidDomainOperationException : DomainException
{
public InvalidDomainOperationException(string message)
: base(message, "INVALID_OPERATION")
{
}
}
using Xunit;
using WIDESEAWCS_Domain.Common;
namespace WIDESEAWCS_Domain.Tests;
public class AggregateRootTests
{
[Fact]
public void Constructor_Should_SetId()
{
// Arrange & Act
var aggregate = new TestAggregate(1);
// Assert
Assert.Equal(1, aggregate.Id);
}
[Fact]
public void AddDomainEvent_Should_AddEvent()
{
// Arrange
var aggregate = new TestAggregate(1);
var domainEvent = new TestDomainEvent();
// Act
aggregate.TestAddDomainEvent(domainEvent);
// Assert
var events = aggregate.GetDomainEvents();
Assert.Single(events);
Assert.Same(domainEvent, events.First());
}
[Fact]
public void ClearDomainEvents_Should_RemoveAllEvents()
{
// Arrange
var aggregate = new TestAggregate(1);
aggregate.TestAddDomainEvent(new TestDomainEvent());
// Act
aggregate.ClearDomainEvents();
// Assert
Assert.Empty(aggregate.GetDomainEvents());
}
[Fact]
public void IncrementVersion_Should_IncreaseVersion()
{
// Arrange
var aggregate = new TestAggregate(1);
var initialVersion = aggregate.Version;
// Act
aggregate.TestIncrementVersion();
// Assert
Assert.Equal(initialVersion + 1, aggregate.Version);
}
// 测试辅助类
private class TestAggregate : AggregateRoot<int>
{
public TestAggregate(int id) : base(id) { }
public void TestAddDomainEvent(IDomainEvent domainEvent)
=> AddDomainEvent(domainEvent);
public void TestIncrementVersion() => IncrementVersion();
}
private record TestDomainEvent : DomainEvent { }
}
Run: dotnet test WIDESEAWCS_Domain.Tests --filter "FullyQualifiedName~AggregateRootTests"
Expected: All tests pass
git add WIDESEAWCS_Domain/
git commit -m "feat: 添加DDD基础类 - AggregateRoot, IDomainEvent, DomainException"
Files:
- Create: WIDESEAWCS_Application/WIDESEAWCS_Application.csproj
- Create: WIDESEAWCS_Application/Common/IRepository.cs
- Create: WIDESEAWCS_Application/Common/IUnitOfWork.cs
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\WIDESEAWCS_Domain\WIDESEAWCS_Domain.csproj" />
</ItemGroup>
</Project>
using WIDESEAWCS_Domain.Common;
namespace WIDESEAWCS_Application.Common;
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);
}
namespace WIDESEAWCS_Application.Common;
public interface IUnitOfWork : IDisposable
{
ITransaction BeginTransaction();
Task Commit();
Task Rollback();
}
public interface ITransaction : IDisposable
{
Task Commit();
Task Rollback();
}
git add WIDESEAWCS_Application/
git commit -m "feat: 添加应用层基础接口 - IRepository, IUnitOfWork"
Files:
- Create: WIDESEAWCS_Infrastructure/WIDESEAWCS_Infrastructure.csproj
- Create: WIDESEAWCS_Infrastructure/Persistence/InMemoryUnitOfWork.cs
- Create: WIDESEAWCS_Infrastructure/Persistence/InMemoryTransaction.cs
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\WIDESEAWCS_Domain\WIDESEAWCS_Domain.csproj" />
<ProjectReference Include="..\WIDESEAWCS_Application\WIDESEAWCS_Application.csproj" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
</ItemGroup>
</Project>
using WIDESEAWCS_Application.Common;
namespace WIDESEAWCS_Infrastructure.Persistence;
public class InMemoryUnitOfWork : IUnitOfWork
{
private readonly List<Action> _operations = new();
public ITransaction BeginTransaction()
{
return new InMemoryTransaction(this);
}
public Task Commit()
{
foreach (var operation in _operations)
{
operation();
}
_operations.Clear();
return Task.CompletedTask;
}
public Task Rollback()
{
_operations.Clear();
return Task.CompletedTask;
}
public void Dispose()
{
_operations.Clear();
}
}
using WIDESEAWCS_Application.Common;
namespace WIDESEAWCS_Infrastructure.Persistence;
public class InMemoryTransaction : ITransaction
{
private readonly InMemoryUnitOfWork _unitOfWork;
private bool _committed;
private bool _rolledBack;
public InMemoryTransaction(InMemoryUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public Task Commit()
{
if (_rolledBack)
throw new InvalidOperationException("Transaction already rolled back");
_committed = true;
return Task.CompletedTask;
}
public Task Rollback()
{
if (_committed)
throw new InvalidOperationException("Transaction already committed");
_rolledBack = true;
return _unitOfWork.Rollback();
}
public void Dispose()
{
if (!_committed && !_rolledBack)
{
Rollback().GetAwaiter().GetResult();
}
}
}
git add WIDESEAWCS_Infrastructure/
git commit -m "feat: 添加基础设施层基础实现 - InMemoryUnitOfWork, InMemoryTransaction"
Files:
- Create: WIDESEAWCS_Domain/DeviceManagement/ValueObjects/DeviceId.cs
- Create: WIDESEAWCS_Domain/DeviceManagement/ValueObjects/DeviceName.cs
- Create: WIDESEAWCS_Domain/DeviceManagement/ValueObjects/DeviceAddress.cs
- Create: WIDESEAWCS_Domain/DeviceManagement/ValueObjects/DeviceType.cs
- Create: WIDESEAWCS_Domain/DeviceManagement/ValueObjects/DeviceStatus.cs
- Test: WIDESEAWCS_Domain.Tests/DeviceManagement/ValueObjects/DeviceValueObjectsTests.cs
namespace WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
public record DeviceId(Guid Value)
{
public static DeviceId New() => new DeviceId(Guid.NewGuid());
public static DeviceId From(Guid value) => new DeviceId(value);
}
namespace WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
public record DeviceName(string Value)
{
public DeviceName(string value)
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("设备名称不能为空", nameof(value));
if (value.Length > 100)
throw new ArgumentException("设备名称长度不能超过100个字符", nameof(value));
Value = value;
}
}
using System.Net;
namespace WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
public record DeviceAddress(string Ip, int Port)
{
public DeviceAddress(string ip, int port)
{
if (!IPAddress.TryParse(ip, out _))
throw new ArgumentException("IP地址格式无效", nameof(ip));
if (port < 1 || port > 65535)
throw new ArgumentException("端口号必须在1-65535之间", nameof(port));
Ip = ip;
Port = port;
}
}
namespace WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
public enum DeviceType
{
StackerCrane = 1, // 堆垛机
ConveyorLine = 2, // 输送线
ShuttleCar = 3, // 穿梭车
Robot = 4, // 机械手
AGV = 5 // 自动导引车
}
namespace WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
public enum DeviceStatus
{
Disconnected = 0, // 未连接
Connecting = 1, // 连接中
Connected = 2, // 已连接
Busy = 3, // 忙碌
Error = 4, // 错误
Maintenance = 5 // 维护中
}
git add WIDESEAWCS_Domain/DeviceManagement/ValueObjects/
git commit -m "feat: 添加设备值对象 - DeviceId, DeviceName, DeviceAddress, DeviceType, DeviceStatus"
Files:
- Create: WIDESEAWCS_Domain/DeviceManagement/Aggregates/Device.cs
- Create: WIDESEAWCS_Domain/DeviceManagement/Events/DeviceConnectedEvent.cs
- Create: WIDESEAWCS_Domain/DeviceManagement/Events/DeviceDisconnectedEvent.cs
- Create: WIDESEAWCS_Domain/DeviceManagement/Events/DeviceHeartbeatEvent.cs
- Test: WIDESEAWCS_Domain.Tests/DeviceManagement/Aggregates/DeviceTests.cs
using WIDESEAWCS_Domain.Common;
using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
namespace WIDESEAWCS_Domain.DeviceManagement.Events;
public record DeviceConnectedEvent : DomainEvent
{
public DeviceId DeviceId { get; init; }
public DateTime ConnectedAt { get; init; } = DateTime.UtcNow;
}
using WIDESEAWCS_Domain.Common;
using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
namespace WIDESEAWCS_Domain.DeviceManagement.Events;
public record DeviceDisconnectedEvent : DomainEvent
{
public DeviceId DeviceId { get; init; }
public string Reason { get; init; } = string.Empty;
}
using WIDESEAWCS_Domain.Common;
using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
namespace WIDESEAWCS_Domain.DeviceManagement.Events;
public record DeviceHeartbeatEvent : DomainEvent
{
public DeviceId DeviceId { get; init; }
public DateTime HeartbeatAt { get; init; } = DateTime.UtcNow;
}
using WIDESEAWCS_Domain.Common;
using WIDESEAWCS_Domain.DeviceManagement.Events;
using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
namespace WIDESEAW_Domain.DeviceManagement.Aggregates;
public class Device : AggregateRoot<DeviceId>
{
private DeviceId _id;
private DeviceName _name;
private DeviceType _type;
private DeviceStatus _status;
private DeviceAddress? _address;
private DateTime _lastConnectedAt;
private DateTime _lastHeartbeatAt;
private string? _errorMessage;
public Device(DeviceId id, DeviceName name, DeviceType type)
{
_id = id;
_name = name;
_type = type;
_status = DeviceStatus.Disconnected;
_lastConnectedAt = DateTime.MinValue;
_lastHeartbeatAt = DateTime.MinValue;
}
// 公共属性访问器
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 string? ErrorMessage => _errorMessage;
// 行为方法
public void Connect()
{
if (_status == DeviceStatus.Connected)
throw new InvalidDomainOperationException("设备已连接,无法重复连接");
_status = DeviceStatus.Connected;
_lastConnectedAt = DateTime.UtcNow;
_errorMessage = null;
IncrementVersion();
AddDomainEvent(new DeviceConnectedEvent
{
DeviceId = _id,
ConnectedAt = _lastConnectedAt
});
}
public void Disconnect(string reason)
{
if (_status == DeviceStatus.Disconnected)
throw new InvalidDomainOperationException("设备已断开,无法重复断开");
_status = DeviceStatus.Disconnected;
_errorMessage = reason;
IncrementVersion();
AddDomainEvent(new DeviceDisconnectedEvent
{
DeviceId = _id,
Reason = reason
});
}
public void UpdateHeartbeat()
{
if (_status != DeviceStatus.Connected && _status != DeviceStatus.Busy)
throw new InvalidDomainOperationException("设备未连接或忙碌,无法更新心跳");
_lastHeartbeatAt = DateTime.UtcNow;
IncrementVersion();
AddDomainEvent(new DeviceHeartbeatEvent
{
DeviceId = _id,
HeartbeatAt = _lastHeartbeatAt
});
}
public void SetAddress(DeviceAddress address)
{
_address = address;
IncrementVersion();
}
// 内部方法(供领域服务使用)
internal void SetStatus(DeviceStatus status)
{
_status = status;
}
internal void SetError(string errorMessage)
{
_status = DeviceStatus.Error;
_errorMessage = errorMessage;
IncrementVersion();
}
}
git add WIDESEAWCS_Domain/DeviceManagement/
git commit -m "feat: 实现设备聚合根和领域事件"
Files:
- Create: WIDESEAWCS_Domain/DeviceManagement/Services/IDeviceStateMachine.cs
- Create: WIDESEAWCS_Domain/DeviceManagement/Services/DeviceStateMachine.cs
using WIDESEAWCS_Domain.DeviceManagement.Aggregates;
using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
namespace WIDESEAWCS_Domain.DeviceManagement.Services;
public interface IDeviceStateMachine
{
bool CanTransition(DeviceStatus current, DeviceStatus target);
void Transition(Device device, DeviceStatus target);
}
using WIDESEAWCS_Domain.DeviceManagement.Aggregates;
using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
using WIDESEAWCS_Domain.Common;
namespace WIDESEAWCS_Domain.DeviceManagement.Services;
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)
{
if (!_transitions.TryGetValue(current, out var allowed))
return false;
return allowed.Contains(target);
}
public void Transition(Device device, DeviceStatus target)
{
if (!CanTransition(device.Status, target))
{
throw new InvalidDomainOperationException(
$"无效的状态转换: {device.Status} -> {target}");
}
device.SetStatus(target);
}
}
git add WIDESEAWCS_Domain/DeviceManagement/Services/
git commit -m "feat: 实现设备状态机领域服务"
Files:
- Create: WIDESEAWCS_Application/DeviceManagement/Repositories/IDeviceRepository.cs
- Create: WIDESEAWCS_Infrastructure/Persistence/Repositories/InMemoryDeviceRepository.cs
using WIDESEAWCS_Application.Common;
using WIDESEAWCS_Domain.DeviceManagement.Aggregates;
using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
namespace WIDESEAWCS_Application.DeviceManagement.Repositories;
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();
}
using WIDESEAWCS_Application.Common;
using WIDESEAWCS_Application.DeviceManagement.Repositories;
using WIDESEAWCS_Domain.DeviceManagement.Aggregates;
using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
namespace WIDESEAWCS_Infrastructure.Persistence.Repositories;
public class InMemoryDeviceRepository : IDeviceRepository
{
private readonly Dictionary<DeviceId, Device> _devices = new();
public Task<Device?> GetById(DeviceId id)
{
_devices.TryGetValue(id, out var device);
return Task.FromResult(device);
}
public Task Add(Device entity)
{
_devices[entity.Id] = entity;
return Task.CompletedTask;
}
public Task Update(Device entity)
{
_devices[entity.Id] = entity;
return Task.CompletedTask;
}
public Task Delete(DeviceId id)
{
_devices.Remove(id);
return Task.CompletedTask;
}
public async Task<Device?> GetByName(DeviceName name)
{
var device = _devices.Values.FirstOrDefault(d => d.Name == name);
return Task.FromResult(device);
}
public async Task<IEnumerable<Device>> GetByType(DeviceType type)
{
var devices = _devices.Values.Where(d => d.Type == type);
return Task.FromResult(devices);
}
public async Task<IEnumerable<Device>> GetByStatus(DeviceStatus status)
{
var devices = _devices.Values.Where(d => d.Status == status);
return Task.FromResult(devices);
}
public async Task<IEnumerable<Device>> GetAllConnected()
{
var devices = _devices.Values.Where(d => d.Status == DeviceStatus.Connected);
return Task.FromResult(devices);
}
}
git add WIDESEAWCS_Application/DeviceManagement/Repositories/ WIDESEAWCS_Infrastructure/Persistence/Repositories/
git commit -m "feat: 实现设备仓储接口和内存实现"
Files:
- Create: WIDESEAWCS_Application/DeviceManagement/Services/DeviceApplicationService.cs
- Create: WIDESEAWCS_Application/DeviceManagement/DTOs/DeviceDto.cs
using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
namespace WIDESEAWCS_Application.DeviceManagement.DTOs;
public record DeviceDto
{
public Guid Id { get; init; }
public string Name { get; init; } = string.Empty;
public DeviceType Type { get; init; }
public DeviceStatus Status { get; init; }
public string? Ip { get; init; }
public int? Port { get; init; }
public DateTime LastConnectedAt { get; init; }
public DateTime LastHeartbeatAt { get; init; }
public string? ErrorMessage { get; init; }
}
using WIDESEAWCS_Application.Common;
using WIDESEAWCS_Application.DeviceManagement.DTOs;
using WIDESEAWCS_Application.DeviceManagement.Repositories;
using WIDESEAWCS_Domain.DeviceManagement.Aggregates;
using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
using WIDESEAWCS_Domain.Common;
namespace WIDESEAWCS_Application.DeviceManagement.Services;
public class DeviceApplicationService
{
private readonly IDeviceRepository _deviceRepository;
private readonly IUnitOfWork _unitOfWork;
public DeviceApplicationService(
IDeviceRepository deviceRepository,
IUnitOfWork unitOfWork)
{
_deviceRepository = deviceRepository;
_unitOfWork = unitOfWork;
}
public async Task<DeviceDto?> GetDevice(Guid id)
{
var deviceId = DeviceId.From(id);
var device = await _deviceRepository.GetById(deviceId);
return device?.ToDto();
}
public async Task<DeviceDto> CreateDevice(string name, DeviceType type, string ip, int port)
{
await using var transaction = _unitOfWork.BeginTransaction();
var deviceId = DeviceId.New();
var deviceName = new DeviceName(name);
var device = new Device(deviceId, deviceName, type);
device.SetAddress(new DeviceAddress(ip, port));
await _deviceRepository.Add(device);
await transaction.Commit();
await _unitOfWork.Commit();
return device.ToDto();
}
public async Task ConnectDevice(Guid id)
{
var deviceId = DeviceId.From(id);
var device = await _deviceRepository.GetById(deviceId);
if (device == null)
throw new DomainException($"设备不存在: {id}", "DEVICE_NOT_FOUND");
device.Connect();
await _deviceRepository.Update(device);
}
public async Task DisconnectDevice(Guid id, string reason)
{
var deviceId = DeviceId.From(id);
var device = await _deviceRepository.GetById(deviceId);
if (device == null)
throw new DomainException($"设备不存在: {id}", "DEVICE_NOT_FOUND");
device.Disconnect(reason);
await _deviceRepository.Update(device);
}
public async Task<IEnumerable<DeviceDto>> GetConnectedDevices()
{
var devices = await _deviceRepository.GetAllConnected();
return devices.Select(d => d.ToDto());
}
}
// 在 WIDESEAWCS_Domain/DeviceManagement/Aggregates/DeviceExtensions.cs 中创建
using WIDESEAWCS_Application.DeviceManagement.DTOs;
namespace WIDESEAWCS_Domain.DeviceManagement.Aggregates;
public static class DeviceExtensions
{
public static DeviceDto ToDto(this Device device)
{
return new DeviceDto
{
Id = device.Id.Value,
Name = device.Name.Value,
Type = device.Type,
Status = device.Status,
Ip = device.Address?.Ip,
Port = device.Address?.Port,
LastConnectedAt = device.LastConnectedAt,
LastHeartbeatAt = device.LastHeartbeatAt,
ErrorMessage = device.ErrorMessage
};
}
}
git add WIDESEAWCS_Application/DeviceManagement/ WIDESEAWCS_Domain/DeviceManagement/Aggregates/DeviceExtensions.cs
git commit -m "feat: 实现设备应用服务和DTO"
Files:
- Create: WIDESEAWCS_Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs
using Microsoft.Extensions.DependencyInjection;
using WIDESEAWCS_Application.Common;
using WIDESEAWCS_Application.DeviceManagement.Repositories;
using WIDESEAWCS_Application.DeviceManagement.Services;
using WIDESEAWCS_Infrastructure.Persistence;
using WIDESEAWCS_Infrastructure.Persistence.Repositories;
using WIDESEAWCS_Domain.DeviceManagement.Services;
namespace WIDESEAWCS_Infrastructure.DependencyInjection;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddDeviceManagementServices(this IServiceCollection services)
{
// 注册领域服务
services.AddSingleton<IDeviceStateMachine, DeviceStateMachine>();
// 注册仓储
services.AddSingleton<IDeviceRepository, InMemoryDeviceRepository>();
// 注册工作单元
services.AddSingleton<IUnitOfWork, InMemoryUnitOfWork>();
// 注册应用服务
services.AddScoped<DeviceApplicationService>();
return services;
}
}
git add WIDESEAWCS_Infrastructure/DependencyInjection/
git commit -m "feat: 配置设备管理服务依赖注入"
Files:
- Create: WIDESEAWCS_IntegrationTests/DeviceManagement/DeviceIntegrationTests.cs
using Xunit;
using Microsoft.Extensions.DependencyInjection;
using WIDESEAWCS_Application.DeviceManagement.Services;
using WIDESEAWCS_Domain.DeviceManagement.Aggregates;
using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
using WIDESEAWCS_Infrastructure.DependencyInjection;
namespace WIDESEAWCS_IntegrationTests.DeviceManagement;
public class DeviceIntegrationTests
{
private readonly IServiceProvider _serviceProvider;
private readonly DeviceApplicationService _service;
public DeviceIntegrationTests()
{
var services = new ServiceCollection();
services.AddDeviceManagementServices();
_serviceProvider = services.BuildServiceProvider();
_service = _serviceProvider.GetRequiredService<DeviceApplicationService>();
}
[Fact]
public async Task DeviceWorkflow_Should_CompleteSuccessfully()
{
// Arrange & Act - 创建设备
var device = await _service.CreateDevice("IntegrationTestDevice", DeviceType.StackerCrane, "127.0.0.1", 8080);
Assert.NotEqual(Guid.Empty, device.Id);
Assert.Equal(DeviceType.StackerCrane, device.Type);
Assert.Equal("IntegrationTestDevice", device.Name.Value);
Assert.Equal(DeviceStatus.Disconnected, device.Status);
// Act - 连接设备
await _service.ConnectDevice(device.Id);
var connectedDevice = await _service.GetDevice(device.Id);
Assert.NotNull(connectedDevice);
Assert.Equal(DeviceStatus.Connected, connectedDevice!.Status);
// Act - 断开设备
await _service.DisconnectDevice(device.Id, "Integration test disconnect");
var disconnectedDevice = await _service.GetDevice(device.Id);
Assert.NotNull(disconnectedDevice);
Assert.Equal(DeviceStatus.Disconnected, disconnectedDevice!.Status);
Assert.Equal("Integration test disconnect", disconnectedDevice!.ErrorMessage);
}
// Act - 获取已连接设备
var connectedDevices = await _service.GetConnectedDevices();
Assert.Single(connectedDevices);
Assert.Equal("IntegrationTestDevice", connectedDevices.First().Name);
}
[Fact]
public async Task GetConnectedDevices_Should_ReturnOnlyConnected()
{
// Arrange & Act - 创建多个设备
var device1 = await _service.CreateDevice("Device1", DeviceType.ConveyorLine, "127.0.0.2", 8081);
var device2 = await _service.CreateDevice("Device2", DeviceType.ShuttleCar, "127.0.0.3", 8082);
// Act - 只连接第一个设备
await _service.ConnectDevice(device1.Id);
var connectedDevices = await _service.GetConnectedDevices();
Assert.Single(connectedDevices);
Assert.Equal("Device1", connectedDevices.First().Name);
}
}
git add WIDESEAWCS_IntegrationTests/DeviceManagement/
git commit -m "test: 添加设备管理集成测试"
Files:
- Modify: CLAUDE.md
在CLAUDE.md中添加:
```markdown
项目采用渐进式DDD架构,包含以下层次:
支持新旧代码共存,可通过配置控制:json "FeatureFlags": { "UseNewDeviceManagement": true, "UseNewTaskScheduler": false }
```
git add CLAUDE.md
git commit -m "docs: 更新CLAUDE.md添加DDD架构说明"
git log --oneline --graph
本次实施计划已完成阶段1-4,共11个任务:
已完成的工作:
1. 基础设施搭建:DDD基础类、应用层接口、基础设施层实现
2. 设备管理领域:设备值对象、聚合根、状态机、仓储
3. 应用层实现:设备应用服务、DTO、扩展方法
4. 集成验证:依赖注入配置、集成测试
5. 文档更新:CLAUDE.md
下一步建议:
1. 编写完整的单元测试和集成测试
2. 实现SqlSugar仓储实现(替换内存实现)
3. 实现缓存优化(Redis集成)
4. 配置特性开关支持新旧代码共存
5. 编写API控制器(DeviceController)
6. 进行性能测试和优化
7. 创建Pull Request合并到master分支