如何结合依赖注入使用内部类以封装业务逻辑仓储?
我来帮你解决这个业务逻辑层封装不严谨的问题,先理清楚你当前的架构痛点,再给出几个落地的优化方案:
你的架构演进与核心痛点
你最初的架构是 API → BusinessLogic → DataAccess,但业务逻辑层里的服务(比如ProjectService)因为耦合了DTO转换,没法复用;于是你新增了Facade层,把原服务改成可复用的仓储,让Facade负责实体和DTO的转换——这步思路是对的,但现在遇到两个核心问题:
- 外部(比如API)可以直接注入仓储,绕过了Facade层
- 想用
internal限制仓储访问,却触发了DI的“注入类可访问性低于使用类”的错误 - 拆分独立项目后,还是觉得业务逻辑的封装不够严格
方案1:友元程序集+内部DI注册,彻底隔离仓储访问
这是最直接的解决方案,利用C#的友元程序集特性,让只有Facade层能访问BusinessLogic里的仓储,同时通过DI的注册控制,确保API层根本接触不到仓储:
步骤拆解:
标记仓储实现为
internal
在BusinessLogic项目里,把仓储的实现类设为internal,接口可以保留public(或者也设为internal,看你是否要完全隐藏):// 仓储接口(可选设为internal,如果不想让Facade看到接口的话) public interface IProjectRepository { Task<Project> GetById(int id); } // 仓储实现设为internal,只有友元程序集能访问 internal class ProjectRepository : IProjectRepository { // 实现逻辑 }给Facade项目开友元权限
在BusinessLogic项目的根目录下添加AssemblyInfo.cs(如果是.NET Core/.NET 5+,也可以用文件级属性),允许Facade项目访问它的internal类型:[assembly: InternalsVisibleTo("YourFacadeProjectName")]注意替换成你实际的Facade项目名称,如果有强签名的话还要加上公钥,不过一般非强签名项目直接写名称就行。
在BusinessLogic里写内部DI扩展
把仓储的注册逻辑封装在BusinessLogic项目的internal扩展方法里,只有友元程序集(Facade)能调用:internal static class BusinessLogicDiExtensions { internal static IServiceCollection AddBusinessLogicRepositories(this IServiceCollection services) { services.AddScoped<IProjectRepository, ProjectRepository>(); // 其他仓储注册 return services; } }Facade层统一暴露DI入口
在Facade项目里写public的DI扩展方法,内部调用BusinessLogic的internal注册方法,同时注册Facade服务:public static class FacadeDiExtensions { public static IServiceCollection AddFacadeServices(this IServiceCollection services) { // 因为友元程序集,这里能调用BusinessLogic的internal扩展方法 services.AddBusinessLogicRepositories(); services.AddScoped<IProjectFacade, ProjectFacade>(); // 其他Facade服务注册 return services; } }API层只引用Facade并注册其服务
API项目只引用Facade项目,在Program.cs里只调用AddFacadeServices(),完全看不到BusinessLogic里的仓储类型:builder.Services.AddFacadeServices();
这样一来,API层根本无法直接注入仓储,甚至连仓储的接口都看不到(如果把接口也设为internal的话),完全只能通过Facade层访问业务逻辑。
方案2:完全封装仓储,只暴露业务逻辑组件
如果想更进一步,让Facade层都看不到仓储,可以把仓储的接口和实现都设为internal,然后在BusinessLogic里暴露面向业务的internal组件,Facade只依赖这些业务组件,而不是直接依赖仓储:
比如在BusinessLogic里写:
// 内部业务逻辑组件,处理实体级别的业务 internal class ProjectManager { private readonly IProjectRepository _repo; public ProjectManager(IProjectRepository repo) { _repo = repo; } public async Task<Project> GetProjectWithValidation(int id) { // 这里可以加业务校验、规则逻辑 var project = await _repo.GetById(id); if (project == null) throw new NotFoundException("Project not found"); return project; } }
然后Facade的ProjectFacade依赖ProjectManager(因为友元程序集能访问),再转换为DTO:
public class ProjectFacade : IProjectFacade { private readonly ProjectManager _projectManager; public ProjectFacade(ProjectManager projectManager) { _projectManager = projectManager; } public async Task<ProjectGetDto> GetProjectById(int id) { var project = await _projectManager.GetProjectWithValidation(id); // 转换为DTO return new ProjectGetDto { Id = project.Id, Name = project.Name }; } }
这种方式下,仓储完全被封装在BusinessLogic内部,连Facade都接触不到,只能使用BusinessLogic提供的业务逻辑组件,封装性更强。
方案3:强化独立项目的边界控制
如果你已经拆分了独立项目,可以再做以下优化来加强封装:
- BusinessLogic项目:只对外暴露必要的类型(比如业务逻辑组件的接口,如果需要的话),其他所有类型(仓储、内部实现类)都设为
internal,通过友元程序集给Facade开权限 - Facade项目:只对外暴露API需要的DTO和Facade服务接口,内部依赖BusinessLogic的组件,不对外暴露任何BusinessLogic的类型
- API项目:仅引用Facade项目,完全不直接引用BusinessLogic和DataAccess项目
这样整个架构的依赖边界非常清晰,每个层只能访问下一层暴露的公开接口,底层的实现细节完全被隐藏。
内容的提问来源于stack exchange,提问作者Stuart Bradley

