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
相关产品推荐
相关产品推荐

