You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# CRUD应用是否需新增领域层与基础设施层?

问题解答

是否需要重构?

肯定需要。当前数据层依赖服务层的设计违反了依赖倒置原则——核心的业务实体(数据层)不应该依赖于外层的服务组件。这种依赖会导致:

  • 实体与服务强耦合,无法独立测试(比如要测试User实体,必须先实例化PasswordService)
  • 数据层变得不纯粹,承担了本该属于业务逻辑或基础设施的职责(密码哈希、文件操作都是业务/基础设施行为,不是实体本身的职责)
  • 后续扩展困难(比如换另一种密码哈希算法,需要修改User实体;换云存储服务商,要改文件实体)

你的重构方案分析

你提出的领域层+基础设施层的思路非常符合整洁架构(Clean Architecture)的核心思想,非常适合学习良好架构原则,具体可优化的点如下:

1. 领域层设计

  • 领域层只放核心实体、业务规则和持久化接口(IUnitOfWork、IRepository等),完全脱离具体实现:
    • User实体只包含核心属性(Id、Username、PasswordHash、PasswordSalt),密码哈希/加盐逻辑放在领域服务(比如UserDomainService),而不是让实体依赖外部服务
    • 文件实体(比如CloudFile)只记录文件的元数据(Id、FileName、StorageKey、OwnerId),文件的上传/下载逻辑由领域服务或基础设施层处理
  • IUnitOfWork要包含数据库操作和云存储操作的抽象,确保业务逻辑层只依赖接口,不关心具体实现

2. 基础设施层实现

  • 基础设施层实现领域层的所有持久化接口:
    • 用EF Core实现数据库部分的IRepository和IUnitOfWork
    • 封装云存储(比如S3)的操作,实现云存储相关的接口(比如IFileStorage),并集成到IUnitOfWork中
    • PasswordService可以放在基础设施层(因为是具体的哈希实现),或者作为领域服务的依赖注入项,由基础设施层提供具体实现

3. 现有层级的调整

  • 原业务逻辑层(基于MediatR)作为应用层,负责协调领域服务和基础设施,处理用户请求、验证输入、返回输出
  • UI层(Razor Pages)保持不变,只依赖应用层的MediatR请求

示例代码片段

领域层实体与接口

// 领域实体
public class User
{
    public Guid Id { get; private set; }
    public string Username { get; private set; }
    public string PasswordHash { get; private set; }
    public string PasswordSalt { get; private set; }

    // 领域方法,只处理核心业务规则
    private User() { }
    public static User Create(string username, string passwordHash, string passwordSalt)
    {
        if (string.IsNullOrEmpty(username))
            throw new ArgumentException("Username cannot be empty");
        return new User
        {
            Id = Guid.NewGuid(),
            Username = username,
            PasswordHash = passwordHash,
            PasswordSalt = passwordSalt
        };
    }
}

// 持久化接口
public interface IUnitOfWork
{
    IRepository<User> Users { get; }
    IFileStorage FileStorage { get; }
    Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}

public interface IFileStorage
{
    Task<string> UploadFileAsync(Stream fileStream, string fileName, CancellationToken cancellationToken = default);
    Task<Stream> DownloadFileAsync(string storageKey, CancellationToken cancellationToken = default);
}

基础设施层实现

// EF Core实现的Repository
public class EfCoreRepository<T> : IRepository<T> where T : class
{
    private readonly AppDbContext _dbContext;
    public EfCoreRepository(AppDbContext dbContext) => _dbContext = dbContext;

    // 实现Repository方法...
}

// S3文件存储实现
public class S3FileStorage : IFileStorage
{
    private readonly AmazonS3Client _s3Client;
    private readonly string _bucketName;

    public S3FileStorage(AmazonS3Client s3Client, IConfiguration configuration)
    {
        _s3Client = s3Client;
        _bucketName = configuration["S3:BucketName"];
    }

    public async Task<string> UploadFileAsync(Stream fileStream, string fileName, CancellationToken cancellationToken)
    {
        var request = new PutObjectRequest
        {
            BucketName = _bucketName,
            Key = fileName,
            InputStream = fileStream
        };
        var response = await _s3Client.PutObjectAsync(request, cancellationToken);
        return fileName;
    }

    // 实现DownloadFileAsync...
}

// UnitOfWork实现
public class UnitOfWork : IUnitOfWork
{
    private readonly AppDbContext _dbContext;
    private readonly IFileStorage _fileStorage;
    private IRepository<User> _users;

    public UnitOfWork(AppDbContext dbContext, IFileStorage fileStorage)
    {
        _dbContext = dbContext;
        _fileStorage = fileStorage;
    }

    public IRepository<User> Users => _users ??= new EfCoreRepository<User>(_dbContext);
    public IFileStorage FileStorage => _fileStorage;

    public async Task<int> SaveChangesAsync(CancellationToken cancellationToken)
    {
        return await _dbContext.SaveChangesAsync(cancellationToken);
    }
}

应用层MediatR处理逻辑

public class CreateUserCommand : IRequest<CreateUserResponse>
{
    public string Username { get; set; }
    public string Password { get; set; }
}

public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, CreateUserResponse>
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly IPasswordHasher _passwordHasher;

    public CreateUserCommandHandler(IUnitOfWork unitOfWork, IPasswordHasher passwordHasher)
    {
        _unitOfWork = unitOfWork;
        _passwordHasher = passwordHasher;
    }

    public async Task<CreateUserResponse> Handle(CreateUserCommand request, CancellationToken cancellationToken)
    {
        // 验证输入
        if (string.IsNullOrEmpty(request.Password))
            throw new ArgumentException("Password cannot be empty");

        // 密码哈希
        var (hash, salt) = _passwordHasher.HashPassword(request.Password);

        // 创建领域实体
        var user = User.Create(request.Username, hash, salt);

        // 保存到数据库
        await _unitOfWork.Users.AddAsync(user, cancellationToken);
        await _unitOfWork.SaveChangesAsync(cancellationToken);

        return new CreateUserResponse { UserId = user.Id };
    }
}

内容的提问来源于stack exchange,提问作者Seth Shoemaker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 11:42:22