编辑 | blame | 历史 | 原始文档

WCS DDD重构实施计划

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(依赖注入)


Chunk 1: 项目结构搭建和基础设施类

Task 1: 创建领域层项目结构

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

  • [ ] Step 1: 创建领域层项目文件
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>
  • [ ] Step 2: 创建聚合根基类
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++;
    }
}
  • [ ] Step 3: 创建领域事件接口
namespace WIDESEAWCS_Domain.Common;

public interface IDomainEvent
{
    DateTime OccurredOn { get; }
}
  • [ ] Step 4: 创建领域事件基类
namespace WIDESEAWCS_Domain.Common;

public abstract record DomainEvent : IDomainEvent
{
    public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
}
  • [ ] Step 5: 创建领域异常类
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")
    {
    }
}
  • [ ] Step 6: 创建聚合根基类测试
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 { }
}
  • [ ] Step 7: 运行测试验证基础类

Run: dotnet test WIDESEAWCS_Domain.Tests --filter "FullyQualifiedName~AggregateRootTests"
Expected: All tests pass

  • [ ] Step 8: 提交基础类
git add WIDESEAWCS_Domain/
git commit -m "feat: 添加DDD基础类 - AggregateRoot, IDomainEvent, DomainException"

Task 2: 创建应用层项目结构

Files:
- Create: WIDESEAWCS_Application/WIDESEAWCS_Application.csproj
- Create: WIDESEAWCS_Application/Common/IRepository.cs
- Create: WIDESEAWCS_Application/Common/IUnitOfWork.cs

  • [ ] Step 1: 创建应用层项目文件
<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>
  • [ ] Step 2: 创建仓储基接口
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);
}
  • [ ] Step 3: 创建工作单元接口
namespace WIDESEAWCS_Application.Common;

public interface IUnitOfWork : IDisposable
{
    ITransaction BeginTransaction();
    Task Commit();
    Task Rollback();
}

public interface ITransaction : IDisposable
{
    Task Commit();
    Task Rollback();
}
  • [ ] Step 4: 提交应用层基础接口
git add WIDESEAWCS_Application/
git commit -m "feat: 添加应用层基础接口 - IRepository, IUnitOfWork"

Task 3: 创建基础设施层项目结构

Files:
- Create: WIDESEAWCS_Infrastructure/WIDESEAWCS_Infrastructure.csproj
- Create: WIDESEAWCS_Infrastructure/Persistence/InMemoryUnitOfWork.cs
- Create: WIDESEAWCS_Infrastructure/Persistence/InMemoryTransaction.cs

  • [ ] Step 1: 创建基础设施层项目文件**
<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>
  • [ ] Step 2: 创建内存工作单元实现
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();
    }
}
  • [ ] Step 3: 创建内存事务实现
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();
        }
    }
}
  • [ ] Step 4: 提交基础设施层基础实现
git add WIDESEAWCS_Infrastructure/
git commit -m "feat: 添加基础设施层基础实现 - InMemoryUnitOfWork, InMemoryTransaction"

Chunk 2: 设备管理领域实现

Task 4: 实现设备值对象

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

  • [ ] Step 1: 创建设备ID值对象
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);
}
  • [ ] Step 2: 创建设备名称值对象
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;
    }
}
  • [ ] Step 3: 创建设备地址值对象
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;
    }
}
  • [ ] Step 4: 创建设备类型枚举
namespace WIDESEAWCS_Domain.DeviceManagement.ValueObjects;

public enum DeviceType
{
    StackerCrane = 1,    // 堆垛机
    ConveyorLine = 2,    // 输送线
    ShuttleCar = 3,      // 穿梭车
    Robot = 4,           // 机械手
    AGV = 5              // 自动导引车
}
  • [ ] Step 5: 创建设备状态枚举
namespace WIDESEAWCS_Domain.DeviceManagement.ValueObjects;

public enum DeviceStatus
{
    Disconnected = 0,    // 未连接
    Connecting = 1,      // 连接中
    Connected = 2,       // 已连接
    Busy = 3,            // 忙碌
    Error = 4,           // 错误
    Maintenance = 5      // 维护中
}
  • [ ] Step 6: 提交设备值对象
git add WIDESEAWCS_Domain/DeviceManagement/ValueObjects/
git commit -m "feat: 添加设备值对象 - DeviceId, DeviceName, DeviceAddress, DeviceType, DeviceStatus"

Task 5: 实现设备聚合根

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

  • [ ] Step 1: 创建设备连接事件
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;
}
  • [ ] Step 2: 创建设备断开事件
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;
}
  • [ ] Step 3: 创建设备心跳事件
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;
}
  • [ ] Step 4: 创建设备聚合根类
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();
    }
}
  • [ ] Step 5: 提交设备聚合根实现
git add WIDESEAWCS_Domain/DeviceManagement/
git commit -m "feat: 实现设备聚合根和领域事件"

Task 6: 实现设备状态机

Files:
- Create: WIDESEAWCS_Domain/DeviceManagement/Services/IDeviceStateMachine.cs
- Create: WIDESEAWCS_Domain/DeviceManagement/Services/DeviceStateMachine.cs

  • [ ] Step 1: 创建设备状态机接口
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);
}
  • [ ] Step 2: 创建设备状态机实现
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);
    }
}
  • [ ] Step 3: 提交设备状态机实现
git add WIDESEAWCS_Domain/DeviceManagement/Services/
git commit -m "feat: 实现设备状态机领域服务"

Task 7: 实现设备仓储接口和实现

Files:
- Create: WIDESEAWCS_Application/DeviceManagement/Repositories/IDeviceRepository.cs
- Create: WIDESEAWCS_Infrastructure/Persistence/Repositories/InMemoryDeviceRepository.cs

  • [ ] Step 1: 创建设备仓储接口
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();
}
  • [ ] Step 2: 创建内存设备仓储实现
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);
    }
}
  • [ ] Step 3: 提交设备仓储实现
git add WIDESEAWCS_Application/DeviceManagement/Repositories/ WIDESEAWCS_Infrastructure/Persistence/Repositories/
git commit -m "feat: 实现设备仓储接口和内存实现"

Chunk 3: 应用服务实现

Task 8: 实现设备应用服务

Files:
- Create: WIDESEAWCS_Application/DeviceManagement/Services/DeviceApplicationService.cs
- Create: WIDESEAWCS_Application/DeviceManagement/DTOs/DeviceDto.cs

  • [ ] Step 1: 创建设备DTO
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; }
}
  • [ ] Step 2: 创建设备应用服务
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());
    }
}
  • [ ] Step 3: 在Device类中添加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
        };
    }
}
  • [ ] Step 4: 提交设备应用服务实现
git add WIDESEAWCS_Application/DeviceManagement/ WIDESEAWCS_Domain/DeviceManagement/Aggregates/DeviceExtensions.cs
git commit -m "feat: 实现设备应用服务和DTO"

Chunk 4: 集成和验证

Task 9: 配置依赖注入

Files:
- Create: WIDESEAWCS_Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs

  • [ ] Step 1: 创建服务注册扩展
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;
    }
}
  • [ ] Step 2: 提交依赖注入配置
git add WIDESEAWCS_Infrastructure/DependencyInjection/
git commit -m "feat: 配置设备管理服务依赖注入"

Task 10: 集成测试

Files:
- Create: WIDESEAWCS_IntegrationTests/DeviceManagement/DeviceIntegrationTests.cs

  • [ ] Step 1: 创建集成测试
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);
    }
}
  • [ ] Step 2: 提交集成测试
git add WIDESEAWCS_IntegrationTests/DeviceManagement/
git commit -m "test: 添加设备管理集成测试"

Task 11: 更新文档

Files:
- Modify: CLAUDE.md

  • [ ] Step 1: 更新CLAUDE.md添加DDD架构说明

在CLAUDE.md中添加:
```markdown

DDD架构实施

项目采用渐进式DDD架构,包含以下层次:

领域层 (WIDESEAWCS_Domain)

  • 聚合根:Device, DeviceTask等
  • 值对象:DeviceId, DeviceName, DeviceAddress等
  • 领域服务:DeviceStateMachine, DeviceScheduler等
  • 领域事件:DeviceConnectedEvent, TaskCompletedEvent等

应用层 (WIDESEAWCS_Application)

  • 应用服务:DeviceApplicationService等
  • DTOs:设备、任务等数据传输对象
  • 仓储接口:IDeviceRepository, ITaskRepository等
  • 工作单元:IUnitOfWork

基础设施层 (WIDESEAWCS_Infrastructure)

  • 仓储实现:InMemoryDeviceRepository, DeviceRepository等
  • 数据持久化:SqlSugar集成
  • 缓存:Redis缓存实现
  • 通信:设备通信适配器

特性开关

支持新旧代码共存,可通过配置控制:
json "FeatureFlags": { "UseNewDeviceManagement": true, "UseNewTaskScheduler": false }
```

  • [ ] Step 2: 提交文档更新
git add CLAUDE.md
git commit -m "docs: 更新CLAUDE.md添加DDD架构说明"

Task 12: 总结和后续步骤

  • [ ] Step 1: 查看所有提交
git log --oneline --graph
  • [ ] Step 2: 显示总结信息

本次实施计划已完成阶段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分支