Files
DMS/软件设计文档/02-DMS.Application - 应用服务与DTOs.md

16 KiB
Raw Permalink Blame History

软件开发文档 - DMS.Application - 应用服务与DTOs

本文档详细阐述了 DMS.Application 层的设计。该层作为业务逻辑的协调者,负责处理用例、数据转换,并作为表现层与核心层的桥梁。

1. 目录结构

DMS.Application/
├── DTOs/
│   ├── DeviceDto.cs
│   ├── CreateDeviceDto.cs
│   ├── UpdateDeviceDto.cs
│   ├── VariableDto.cs
│   ├── VariableTableDto.cs
│   ├── MqttServerDto.cs
│   ├── VariableMqttAliasDto.cs
│   └── CreateDeviceWithDetailsDto.cs
├── Interfaces/
│   ├── IDeviceAppService.cs
│   ├── IMqttAppService.cs
│   ├── IVariableAppService.cs
│   ├── IMqttAliasAppService.cs
│   └── IMenuService.cs
├── Services/
│   ├── DeviceAppService.cs
│   ├── MqttAppService.cs
│   ├── VariableAppService.cs
│   ├── MqttAliasAppService.cs
│   └── MenuService.cs
├── Profiles/
│   └── MappingProfile.cs
└── DMS.Application.csproj

2. 应用服务设计

2.1. 设计思路与考量

  • 职责应用服务Application Services是应用程序的用例Use Case实现者。它们负责编排领域模型和仓储来完成特定的业务操作处理事务、授权、日志等应用级关注点。
  • 依赖:应用服务依赖于 DMS.Core 中定义的接口(如 IRepositoryManager),而不直接依赖 DMS.Infrastructure 的具体实现,遵循依赖倒置原则。
  • 事务管理:通过注入 IRepositoryManager 来管理事务,确保业务操作的原子性。

2.2. 设计优势

  • 业务流程清晰:每个应用服务方法对应一个具体的业务用例,代码结构清晰,易于理解业务流程。
  • 解耦将业务逻辑与UI层和数据访问层解耦提高了代码的可维护性和可测试性。
  • 事务边界明确:应用服务是定义事务边界的理想位置,确保业务操作的完整性。
  • 可重用性应用服务可以被不同的客户端如WPF UI、Web API等复用。

2.3. 设计劣势/权衡

  • 代码量增加相比于直接在UI层或仓储层编写业务逻辑应用服务模式会增加一些代码量和抽象层级。
  • 贫血应用服务:如果应用服务仅仅是简单地调用仓储方法,而没有包含任何业务逻辑,则可能退化为“贫血应用服务”,失去了其应有的价值。

2.4. 示例:IDeviceAppService.cs

// 文件: DMS.Application/Interfaces/IDeviceAppService.cs
using DMS.Application.DTOs;
using DMS.Core.Enums;

namespace DMS.Application.Interfaces;

/// <summary>
/// 定义设备管理相关的应用服务操作。
/// </summary>
public interface IDeviceAppService
{
    /// <summary>
    /// 异步根据ID获取设备DTO。
    /// </summary>
    Task<DeviceDto> GetDeviceByIdAsync(int id);

    /// <summary>
    /// 异步获取所有设备DTO列表。
    /// </summary>
    Task<List<DeviceDto>> GetAllDevicesAsync();

    /// <summary>
    /// 异步创建一个新设备及其关联的变量表和菜单(事务性操作)。
    /// </summary>
    /// <param name="dto">包含设备、变量表和菜单信息的DTO。</param>
    /// <returns>新创建设备的ID。</returns>
    Task<int> CreateDeviceWithDetailsAsync(CreateDeviceWithDetailsDto dto);

    /// <summary>
    /// 异步更新一个已存在的设备。
    /// </summary>
    Task UpdateDeviceAsync(UpdateDeviceDto deviceDto);

    /// <summary>
    /// 异步删除一个设备。
    /// </summary>
    Task DeleteDeviceAsync(int id);

    /// <summary>
    /// 异步切换设备的激活状态。
    /// </summary>
    Task ToggleDeviceActiveStateAsync(int id);

    /// <summary>
    /// 异步获取指定协议类型的设备列表。
    /// </summary>
    Task<List<DeviceDto>> GetDevicesByProtocolAsync(ProtocolType protocol);
}

2.5. 示例:DeviceAppService.cs

// 文件: DMS.Application/Services/DeviceAppService.cs
using AutoMapper;
using DMS.Core.Interfaces;
using DMS.Core.Models;

namespace DMS.Application.Services;

/// <summary>
/// 实现设备管理的应用服务。
/// </summary>
public class DeviceAppService : IDeviceAppService
{
    private readonly IRepositoryManager _repoManager;
    private readonly IMapper _mapper;

    /// <summary>
    /// 构造函数通过依赖注入获取仓储管理器和AutoMapper实例。
    /// </summary>
    public DeviceAppService(IRepositoryManager repoManager, IMapper mapper)
    {
        _repoManager = repoManager;
        _mapper = mapper;
    }

    /// <summary>
    /// 异步创建一个新设备及其关联的变量表和菜单(事务性操作)。
    /// </summary>
    public async Task<int> CreateDeviceWithDetailsAsync(CreateDeviceWithDetailsDto dto)
    {
        try
        {
            _repoManager.BeginTransaction();

            var device = _mapper.Map<Device>(dto.Device);
            device.IsActive = true; // 默认激活
            await _repoManager.Devices.AddAsync(device);

            // 假设 CreateDeviceWithDetailsDto 包含了变量表和菜单信息
            if (dto.VariableTable != null)
            {
                var variableTable = _mapper.Map<VariableTable>(dto.VariableTable);
                variableTable.DeviceId = device.Id; // 关联新设备ID
                await _repoManager.VariableTables.AddAsync(variableTable);
            }

            // 假设有菜单服务或仓储
            // if (dto.Menu != null)
            // {
            //     var menu = _mapper.Map<Menu>(dto.Menu);
            //     menu.TargetId = device.Id;
            //     await _repoManager.Menus.AddAsync(menu);
            // }

            await _repoManager.CommitAsync();

            return device.Id;
        }
        catch (Exception ex)
        {
            await _repoManager.RollbackAsync();
            // 可以在此记录日志
            throw new ApplicationException("创建设备时发生错误,操作已回滚。", ex);
        }
    }

    /// <summary>
    /// 异步获取所有设备DTO列表。
    /// </summary>
    public async Task<List<DeviceDto>> GetAllDevicesAsync()
    {
        var devices = await _repoManager.Devices.GetAllAsync();
        return _mapper.Map<List<DeviceDto>>(devices);
    }

    // ... 其他方法的实现
}

3. 数据传输对象 (DTOs)

3.1. 设计思路与考量

  • 职责DTOs (Data Transfer Objects) 是用于在不同层之间(特别是应用层和表现层之间)传输数据的简单对象。它们通常是扁平的,只包含数据属性,不包含行为。
  • 隔离DTOs 隔离了领域模型和UI层避免了领域模型直接暴露给UI从而保护了领域模型的完整性和不变性。
  • 定制化DTOs 可以根据UI的需求进行定制例如只包含UI需要显示的字段或者将多个领域模型的字段组合成一个DTO。

3.2. 设计优势

  • 解耦UI层不直接依赖领域模型领域模型的修改不会直接影响UI层。
  • 安全性避免了敏感数据或不必要的复杂领域逻辑暴露给UI层。
  • 性能优化可以只传输UI所需的数据减少网络传输量对于分布式系统
  • 简化UI绑定DTOs 通常是扁平的更适合UI的数据绑定。

3.3. 设计劣势/权衡

  • 映射开销需要在领域模型和DTO之间进行映射增加了代码量和运行时开销尽管AutoMapper可以简化
  • DTOs 膨胀如果每个UI视图都需要一个独立的DTO可能导致DTO类的数量膨胀。

3.4. 示例:DeviceDto.cs

// 文件: DMS.Application/DTOs/DeviceDto.cs
namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示设备基本信息的DTO。
/// </summary>
public class DeviceDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Protocol { get; set; }
    public string IpAddress { get; set; }
    public int Port { get; set; }
    public int Rack { get; set; }
    public int Slot { get; set; }
    public string OpcUaServerUrl { get; set; }
    public bool IsActive { get; set; }
    public string Status { get; set; } // "在线", "离线", "连接中..."
}

3.5. 示例:CreateDeviceDto.cs

// 文件: DMS.Application/DTOs/CreateDeviceDto.cs
using DMS.Core.Enums;

namespace DMS.Application.DTOs;

/// <summary>
/// 用于创建新设备时传输数据的DTO。
/// </summary>
public class CreateDeviceDto
{
    public string Name { get; set; }
    public ProtocolType Protocol { get; set; }
    public string IpAddress { get; set; }
    public int Port { get; set; }
}

3.6. 示例:VariableMqttAliasDto.cs

// 文件: DMS.Application/DTOs/VariableMqttAliasDto.cs
namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示和管理变量与MQTT服务器关联别名的DTO。
/// </summary>
public class VariableMqttAliasDto
{
    public int Id { get; set; }
    public int VariableId { get; set; }
    public int MqttServerId { get; set; }
    public string MqttServerName { get; set; } // 用于UI显示关联的服务器名称
    public string Alias { get; set; }
}

3.7. 示例:VariableDto.cs

// 文件: DMS.Application/DTOs/VariableDto.cs
using DMS.Core.Enums;
using System;

namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示变量基本信息的DTO。
/// </summary>
public class VariableDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public SignalType DataType { get; set; }
    public PollLevelType PollLevel { get; set; }
    public bool IsActive { get; set; }
    public int VariableTableId { get; set; }
    public string OpcUaNodeId { get; set; }
    public bool IsHistoryEnabled { get; set; }
    public double HistoryDeadband { get; set; }
    public bool IsAlarmEnabled { get; set; }
    public double AlarmMinValue { get; set; }
    public double AlarmMaxValue { get; set; }
    public double AlarmDeadband { get; set; }
    public ProtocolType Protocol { get; set; }
    public CSharpDataType CSharpDataType { get; set; }
    public string ConversionFormula { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime UpdatedAt { get; set; }
    public string UpdatedBy { get; set; }
    public bool IsModified { get; set; }
}

3.8. 示例:MqttServerDto.cs

// 文件: DMS.Application/DTOs/MqttServerDto.cs
using System;

namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示MQTT服务器配置信息的DTO。
/// </summary>
public class MqttServerDto
{
    public int Id { get; set; }
    public string ServerName { get; set; }
    public string BrokerAddress { get; set; }
    public int Port { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public bool IsActive { get; set; }
    public string SubscribeTopic { get; set; }
    public string PublishTopic { get; set; }
    public string ClientId { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? ConnectedAt { get; set; }
    public long ConnectionDuration { get; set; }
    public string MessageFormat { get; set; }
}

3.9. 示例:VariableTableDto.cs

// 文件: DMS.Application/DTOs/VariableTableDto.cs
using DMS.Core.Enums;

namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示变量表基本信息的DTO。
/// </summary>
public class VariableTableDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public bool IsActive { get; set; }
    public int DeviceId { get; set; }
    public ProtocolType Protocol { get; set; }
}

3.10. 示例:VariableHistoryDto.cs

// 文件: DMS.Application/DTOs/VariableHistoryDto.cs
using System;

namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示变量历史记录的DTO。
/// </summary>
public class VariableHistoryDto
{
    public long Id { get; set; }
    public int VariableId { get; set; }
    public string Value { get; set; }
    public DateTime Timestamp { get; set; }
}

3.11. 示例:MenuBeanDto.cs

// 文件: DMS.Application/DTOs/MenuBeanDto.cs
namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示菜单项的DTO。
/// </summary>
public class MenuBeanDto
{
    public int Id { get; set; }
    public int? ParentId { get; set; }
    public string Header { get; set; }
    public string Icon { get; set; }
    public string TargetViewKey { get; set; }
    public string NavigationParameter { get; set; }
    public int DisplayOrder { get; set; }
}

3.12. 示例:UserDto.cs

// 文件: DMS.Application/DTOs/UserDto.cs
namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示用户信息的DTO。
/// </summary>
public class UserDto
{
    public int Id { get; set; }
    public string Username { get; set; }
    public string Role { get; set; }
    public bool IsActive { get; set; }
}

4. AutoMapper 配置

4.1. 设计思路与考量

  • 自动化映射:使用 AutoMapper 库来自动化领域模型和DTO之间的对象映射减少手动映射的重复代码。
  • 集中配置:所有映射规则集中在一个 MappingProfile 类中进行配置。

4.2. 设计优势

  • 减少样板代码:显著减少了手动进行属性赋值的重复代码。
  • 提高开发效率:开发者可以专注于业务逻辑,而不是数据转换。
  • 可维护性:映射规则集中管理,修改和审查方便。

4.3. 设计劣势/权衡

  • 隐式性映射过程是隐式的对于不熟悉AutoMapper的开发者可能难以理解数据流向。
  • 调试难度:当映射出现问题时,调试可能比手动映射更复杂。
  • 性能开销虽然AutoMapper经过优化但在极端性能敏感的场景下仍然可能比手动映射有轻微的性能开销。

4.4. 示例:MappingProfile.cs

// 文件: DMS.Application/Profiles/MappingProfile.cs
using AutoMapper;
using DMS.Core.Models;
using DMS.Application.DTOs;
using DMS.Core.Enums;

namespace DMS.Application.Profiles;

/// <summary>
/// 配置AutoMapper的映射规则。
/// </summary>
public class MappingProfile : Profile
{
    public MappingProfile()
    {
        // Device 映射
        CreateMap<CreateDeviceDto, Device>();
        CreateMap<UpdateDeviceDto, Device>();
        CreateMap<Device, DeviceDto>()
            .ForMember(dest => dest.Protocol, opt => opt.MapFrom(src => src.Protocol.ToString()));

        // VariableTable 映射
        CreateMap<VariableTable, VariableTableDto>().ReverseMap();

        // Variable 映射
        CreateMap<Variable, VariableDto>()
            .ForMember(dest => dest.DataType, opt => opt.MapFrom(src => src.DataType.ToString()))
            .ForMember(dest => dest.CSharpDataType, opt => opt.MapFrom(src => src.CSharpDataType));

        // MqttServer 映射
        CreateMap<MqttServer, MqttServerDto>().ReverseMap();

        // VariableMqttAlias 映射
        CreateMap<VariableMqttAlias, VariableMqttAliasDto>().ReverseMap();

        // VariableTable 映射
        CreateMap<VariableTable, VariableTableDto>().ReverseMap();

        // VariableHistory 映射
        CreateMap<VariableHistory, VariableHistoryDto>().ReverseMap();

        // MenuBean 映射
        CreateMap<MenuBean, MenuBeanDto>().ReverseMap();

        // User 映射
        CreateMap<User, UserDto>().ReverseMap();
    }
}