CleanArchitecture备忘录模式:对象状态保存

CleanArchitecture备忘录模式:对象状态保存

【免费下载链接】CleanArchitecture CleanArchitecture 是一个基于.NET Core的应用程序模板项目,遵循干净架构原则。它为软件项目提供了一个清晰的分层结构,有助于分离关注点、提升可维护性和重用性。适合用于构建具有良好架构基础的中大型企业应用。 【免费下载链接】CleanArchitecture 项目地址: https://gitcode.com/GitHub_Trending/cl/CleanArchitecture

还在为对象状态回滚而头疼?一文掌握CleanArchitecture中备忘录模式的优雅实现!

在复杂的企业应用中,对象状态管理是一个常见但容易被忽视的挑战。当用户需要撤销操作、恢复历史状态或实现事务性操作时,备忘录模式(Memento Pattern)提供了完美的解决方案。本文将深入探讨如何在CleanArchitecture架构中优雅地实现备忘录模式。

📋 读完本文你将收获

  • ✅ 备忘录模式的核心概念与适用场景
  • ✅ CleanArchitecture中备忘录模式的架构设计
  • ✅ 完整的代码实现与最佳实践
  • ✅ 状态恢复的性能优化策略
  • ✅ 与领域事件、CQRS模式的集成方案

🎯 备忘录模式核心概念

备忘录模式属于行为型设计模式,它允许在不破坏封装性的前提下捕获并外部化对象的内部状态,以便后续可以恢复到此状态。

模式参与者

mermaid

适用场景对比表

场景适用性优势注意事项
撤销/重做操作⭐⭐⭐⭐⭐完美支持多级撤销内存占用需控制
事务性操作⭐⭐⭐⭐保证操作原子性需要持久化支持
状态快照⭐⭐⭐⭐灵活的状态管理状态序列化复杂度
工作流状态⭐⭐⭐支持流程回退状态一致性维护

🏗️ CleanArchitecture中的备忘录模式设计

在CleanArchitecture架构中,备忘录模式需要遵循分层原则,确保各层职责清晰。

架构分层设计

mermaid

核心领域对象设计

在Core层定义备忘录相关的领域模型:

// Core/ContributionAggregate/ContributionMemento.cs
namespace Clean.Architecture.Core.ContributionAggregate;

public class ContributionMemento : ValueObject
{
    public string Name { get; private set; }
    public ContributionStatus Status { get; private set; }
    public DateTime SnapshotTime { get; private set; }
    public string SerializedState { get; private set; }

    public ContributionMemento(string name, ContributionStatus status, string serializedState)
    {
        Name = Guard.Against.NullOrEmpty(name, nameof(name));
        Status = status;
        SerializedState = Guard.Against.NullOrEmpty(serializedState, nameof(serializedState));
        SnapshotTime = DateTime.UtcNow;
    }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Name;
        yield return Status;
        yield return SnapshotTime;
        yield return SerializedState;
    }
}

// Core/Interfaces/IMementoOriginator.cs
public interface IMementoOriginator
{
    ContributionMemento CreateMemento();
    void RestoreFromMemento(ContributionMemento memento);
}

领域对象实现备忘录接口

// Core/ContributionAggregate/Contribution.cs
public class Contribution : EntityBase, IAggregateRoot, IMementoOriginator
{
    // 现有属性...
    public string Name { get; private set; } = default!;
    public ContributionStatus Status { get; private set; } = ContributionStatus.NotSet;
    
    // 备忘录模式实现
    public ContributionMemento CreateMemento()
    {
        var state = new
        {
            Name,
            Status,
            PhoneNumber = PhoneNumber?.ToString()
        };
        
        var serializedState = JsonSerializer.Serialize(state);
        return new ContributionMemento(Name, Status, serializedState);
    }

    public void RestoreFromMemento(ContributionMemento memento)
    {
        var state = JsonSerializer.Deserialize<dynamic>(memento.SerializedState);
        
        Name = state?.Name ?? string.Empty;
        Status = state?.Status ?? ContributionStatus.NotSet;
        
        // 根据需要恢复其他属性
    }
}

🛠️ Use Cases层:备忘录操作用例

在Use Cases层实现具体的备忘录操作命令和查询:

// UseCases/Contributions/Memento/
namespace Clean.Architecture.UseCases.Contributions.Memento;

// 创建备忘录命令
public record CreateContributionMementoCommand(int ContributionId) : IRequest<ContributionMementoDto>;

public class CreateContributionMementoHandler : IRequestHandler<CreateContributionMementoCommand, ContributionMementoDto>
{
    private readonly IRepository<Contribution> _repository;
    private readonly IMementoRepository _mementoRepository;

    public CreateContributionMementoHandler(IRepository<Contribution> repository, IMementoRepository mementoRepository)
    {
        _repository = repository;
        _mementoRepository = mementoRepository;
    }

    public async Task<ContributionMementoDto> Handle(CreateContributionMementoCommand request, CancellationToken cancellationToken)
    {
        var contribution = await _repository.GetByIdAsync(request.ContributionId, cancellationToken);
        if (contribution == null)
            throw new NotFoundException(nameof(Contribution), request.ContributionId);

        var memento = contribution.CreateMemento();
        await _mementoRepository.AddAsync(memento, cancellationToken);

        return new ContributionMementoDto(
            memento.Name,
            memento.Status.ToString(),
            memento.SnapshotTime
        );
    }
}

// 恢复备忘录命令
public record RestoreContributionFromMementoCommand(int ContributionId, Guid MementoId) : IRequest;

public class RestoreContributionFromMementoHandler : IRequestHandler<RestoreContributionFromMementoCommand>
{
    private readonly IRepository<Contribution> _repository;
    private readonly IMementoRepository _mementoRepository;

    public RestoreContributionFromMementoHandler(IRepository<Contribution> repository, IMementoRepository mementoRepository)
    {
        _repository = repository;
        _mementoRepository = mementoRepository;
    }

    public async Task Handle(RestoreContributionFromMementoCommand request, CancellationToken cancellationToken)
    {
        var contribution = await _repository.GetByIdAsync(request.ContributionId, cancellationToken);
        if (contribution == null)
            throw new NotFoundException(nameof(Contribution), request.ContributionId);

        var memento = await _mementoRepository.GetByIdAsync(request.MementoId, cancellationToken);
        if (memento == null)
            throw new NotFoundException(nameof(ContributionMemento), request.MementoId);

        contribution.RestoreFromMemento(memento);
        await _repository.UpdateAsync(contribution, cancellationToken);
    }
}

📊 Infrastructure层:备忘录持久化

在Infrastructure层实现备忘录的存储和序列化:

// Infrastructure/Data/MementoRepository.cs
namespace Clean.Architecture.Infrastructure.Data;

public interface IMementoRepository
{
    Task<ContributionMemento?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
    Task<List<ContributionMemento>> GetByContributionIdAsync(int contributionId, CancellationToken cancellationToken = default);
    Task AddAsync(ContributionMemento memento, CancellationToken cancellationToken = default);
    Task DeleteAsync(Guid id, CancellationToken cancellationToken = default);
}

public class MementoRepository : IMementoRepository
{
    private readonly AppDbContext _dbContext;

    public MementoRepository(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<ContributionMemento?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
    {
        return await _dbContext.Set<ContributionMemento>()
            .FirstOrDefaultAsync(m => m.Id == id, cancellationToken);
    }

    public async Task<List<ContributionMemento>> GetByContributionIdAsync(int contributionId, CancellationToken cancellationToken = default)
    {
        return await _dbContext.Set<ContributionMemento>()
            .Where(m => m.ContributionId == contributionId)
            .OrderByDescending(m => m.SnapshotTime)
            .ToListAsync(cancellationToken);
    }

    public async Task AddAsync(ContributionMemento memento, CancellationToken cancellationToken = default)
    {
        await _dbContext.Set<ContributionMemento>().AddAsync(memento, cancellationToken);
        await _dbContext.SaveChangesAsync(cancellationToken);
    }

    public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default)
    {
        var memento = await GetByIdAsync(id, cancellationToken);
        if (memento != null)
        {
            _dbContext.Set<ContributionMemento>().Remove(memento);
            await _dbContext.SaveChangesAsync(cancellationToken);
        }
    }
}

🌐 Web层:RESTful API端点

在Web层提供备忘录操作的API端点:

// Web/Contributions/Memento/
namespace Clean.Architecture.Web.Contributions.Memento;

public static class MementoEndpoints
{
    public static void MapContributionMementoEndpoints(this IEndpointRouteBuilder routes)
    {
        var group = routes.MapGroup("/api/contributions/{contributionId}/mementos");
        
        group.MapGet("", async (int contributionId, ISender mediator, CancellationToken cancellationToken) =>
        {
            var query = new GetContributionMementosQuery(contributionId);
            var result = await mediator.Send(query, cancellationToken);
            return Results.Ok(result);
        }).WithName("GetContributionMementos");
        
        group.MapPost("", async (int contributionId, ISender mediator, CancellationToken cancellationToken) =>
        {
            var command = new CreateContributionMementoCommand(contributionId);
            var result = await mediator.Send(command, cancellationToken);
            return Results.Created($"/api/contributions/{contributionId}/mementos/{result.Id}", result);
        }).WithName("CreateContributionMemento");
        
        group.MapPost("{mementoId}/restore", async (int contributionId, Guid mementoId, ISender mediator, CancellationToken cancellationToken) =>
        {
            var command = new RestoreContributionFromMementoCommand(contributionId, mementoId);
            await mediator.Send(command, cancellationToken);
            return Results.NoContent();
        }).WithName("RestoreContributionFromMemento");
    }
}

⚡ 性能优化策略

内存管理优化

// 使用轻量级状态序列化
public class OptimizedMementoSerializer
{
    public string SerializeState(Contribution contribution)
    {
        // 使用MessagePack或Protobuf进行高效序列化
        using var stream = new MemoryStream();
        MessagePackSerializer.Serialize(stream, new
        {
            contribution.Name,
            contribution.Status,
            // 只序列化必要字段
        });
        return Convert.ToBase64String(stream.ToArray());
    }
    
    public dynamic DeserializeState(string serializedState)
    {
        var bytes = Convert.FromBase64String(serializedState);
        using var stream = new MemoryStream(bytes);
        return MessagePackSerializer.Deserialize<dynamic>(stream);
    }
}

数据库优化策略

策略实现方式优势适用场景
分表存储按时间分表查询性能提升历史数据量大
压缩存储GZip压缩存储空间节省状态数据较大
增量快照只存储变化存储效率高频繁状态变更
缓存策略Redis缓存读取速度快高频访问

🔄 与领域事件集成

备忘录模式可以与领域事件完美结合,实现更强大的状态管理:

// Core/ContributionAggregate/Events/ContributionMementoCreatedEvent.cs
public class ContributionMementoCreatedEvent : DomainEventBase
{
    public int ContributionId { get; }
    public Guid MementoId { get; }
    public DateTime SnapshotTime { get; }

    public ContributionMementoCreatedEvent(int contributionId, Guid mementoId, DateTime snapshotTime)
    {
        ContributionId = contributionId;
        MementoId = mementoId;
        SnapshotTime = snapshotTime;
    }
}

// 在创建备忘录时发布事件
public ContributionMemento CreateMemento()
{
    var memento = // 创建备忘录逻辑
    AddDomainEvent(new ContributionMementoCreatedEvent(Id, memento.Id, memento.SnapshotTime));
    return memento;
}

🧪 单元测试示例

// UnitTests/Core/ContributionAggregate/ContributionMementoTests.cs
public class ContributionMementoTests
{
    [Fact]
    public void CreateMemento_ShouldCaptureCurrentState()
    {
        // Arrange
        var contribution = new Contribution("Test Contribution");
        contribution.SetPhoneNumber("123-456-7890");
        
        // Act
        var memento = contribution.CreateMemento();
        
        // Assert
        memento.Name.Should().Be("Test Contribution");
        memento.SnapshotTime.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }
    
    [Fact]
    public void RestoreFromMemento_ShouldRecoverPreviousState()
    {
        // Arrange
        var original = new Contribution("Original");
        original.SetPhoneNumber("original-phone");
        var memento = original.CreateMemento();
        
        var restored = new Contribution("New");
        restored.SetPhoneNumber("new-phone");
        
        // Act
        restored.RestoreFromMemento(memento);
        
        // Assert
        restored.Name.Should().Be("Original");
        restored.PhoneNumber.Number.Should().Be("original-phone");
    }
}

🚀 部署与运维考虑

监控指标

mermaid

配置建议

{
  "MementoSettings": {
    "MaxMementosPerEntity": 50,
    "RetentionPeriodDays": 30,
    "CompressionEnabled": true,
    "CacheEnabled": true,
    "CacheDurationMinutes": 5
  }
}

📈 最佳实践总结

  1. 适度使用:只在真正需要状态恢复功能的领域对象上实现备忘录模式
  2. 性能考量:对大对象使用增量快照和压缩策略
  3. 安全考虑:对敏感数据进行加密存储
  4. 清理策略:实现自动清理过期备忘录的机制
  5. 监控告警:设置存储空间和性能监控告警

🎯 下一步行动

  •  在您的Contribution实体中实现IMementoOriginator接口
  •  配置MementoRepository和相关服务
  •  添加备忘录相关的API端点
  •  实现自动清理任务
  •  添加监控和日志记录

备忘录模式在CleanArchitecture中的实现不仅提供了强大的状态管理能力,更重要的是保持了架构的整洁性和可维护性。通过遵循本文的指导,您可以在项目中优雅地实现撤销/重做、事务回滚等复杂功能。

点赞/收藏/关注三连,获取更多CleanArchitecture最佳实践!下期我们将探讨「CleanArchitecture中的策略模式与插件架构」。

【免费下载链接】CleanArchitecture CleanArchitecture 是一个基于.NET Core的应用程序模板项目,遵循干净架构原则。它为软件项目提供了一个清晰的分层结构,有助于分离关注点、提升可维护性和重用性。适合用于构建具有良好架构基础的中大型企业应用。 【免费下载链接】CleanArchitecture 项目地址: https://gitcode.com/GitHub_Trending/cl/CleanArchitecture

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值