Files
DMS/软件设计文档/原始文档/03-应用服务与数据传输对象.md

302 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 03. 应用服务与数据传输对象
本文档定义了 `DMS.Application` 层的最终设计。该层作为业务逻辑的协调者,负责处理用例、数据转换,并作为表现层与核心层的桥梁。
## 1. 目录结构
```
DMS.Application/
├── DTOs/
│ ├── DeviceDto.cs
│ ├── VariableDto.cs
│ ├── VariableMqttAliasDto.cs
│ └── ... (其他增、删、改、查相关的DTO)
├── Interfaces/
│ ├── IDeviceAppService.cs
│ ├── IMqttAliasAppService.cs
│ └── ... (其他应用服务接口)
├── Services/
│ ├── DeviceAppService.cs
│ ├── MqttAliasAppService.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`
```csharp
// 文件: 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`
```csharp
// 文件: 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`
```csharp
// 文件: 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 bool IsActive { get; set; }
public string Status { get; set; } // "在线", "离线", "连接中..."
}
```
### 3.5. 示例:`CreateDeviceDto.cs`
```csharp
// 文件: 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; }
}
```
## 4. AutoMapper 配置
### 4.1. 设计思路与考量
* **自动化映射**:使用 `AutoMapper` 库来自动化领域模型和DTO之间的对象映射减少手动映射的重复代码。
* **集中配置**:所有映射规则集中在一个 `MappingProfile` 类中进行配置。
### 4.2. 设计优势
* **减少样板代码**:显著减少了手动进行属性赋值的重复代码。
* **提高开发效率**:开发者可以专注于业务逻辑,而不是数据转换。
* **可维护性**:映射规则集中管理,修改和审查方便。
### 4.3. 设计劣势/权衡
* **隐式性**映射过程是隐式的对于不熟悉AutoMapper的开发者可能难以理解数据流向。
* **调试难度**:当映射出现问题时,调试可能比手动映射更复杂。
* **性能开销**虽然AutoMapper经过优化但在极端性能敏感的场景下仍然可能比手动映射有轻微的性能开销。
### 4.4. 示例:`MappingProfile.cs`
```csharp
// 文件: 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()));
// MqttServer 映射
CreateMap<MqttServer, MqttServerDto>().ReverseMap();
// VariableMqttAlias 映射
CreateMap<VariableMqttAlias, VariableMqttAliasDto>().ReverseMap();
// 其他复杂DTO的映射可能需要更详细的配置
CreateMap<CreateDeviceWithDetailsDto, Device>(); // 示例从复合DTO映射到领域模型
}
}
```