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

如何结合依赖注入使用内部类以封装业务逻辑仓储?

我来帮你解决这个业务逻辑层封装不严谨的问题,先理清楚你当前的架构痛点,再给出几个落地的优化方案:


你的架构演进与核心痛点

你最初的架构是 API → BusinessLogic → DataAccess,但业务逻辑层里的服务(比如ProjectService)因为耦合了DTO转换,没法复用;于是你新增了Facade层,把原服务改成可复用的仓储,让Facade负责实体和DTO的转换——这步思路是对的,但现在遇到两个核心问题:

  1. 外部(比如API)可以直接注入仓储,绕过了Facade层
  2. 想用internal限制仓储访问,却触发了DI的“注入类可访问性低于使用类”的错误
  3. 拆分独立项目后,还是觉得业务逻辑的封装不够严格

方案1:友元程序集+内部DI注册,彻底隔离仓储访问

这是最直接的解决方案,利用C#的友元程序集特性,让只有Facade层能访问BusinessLogic里的仓储,同时通过DI的注册控制,确保API层根本接触不到仓储:

步骤拆解:

  1. 标记仓储实现为internal
    在BusinessLogic项目里,把仓储的实现类设为internal,接口可以保留public(或者也设为internal,看你是否要完全隐藏):

    // 仓储接口(可选设为internal,如果不想让Facade看到接口的话)
    public interface IProjectRepository
    {
        Task<Project> GetById(int id);
    }
    
    // 仓储实现设为internal,只有友元程序集能访问
    internal class ProjectRepository : IProjectRepository
    {
        // 实现逻辑
    }
    
  2. 给Facade项目开友元权限
    在BusinessLogic项目的根目录下添加AssemblyInfo.cs(如果是.NET Core/.NET 5+,也可以用文件级属性),允许Facade项目访问它的internal类型:

    [assembly: InternalsVisibleTo("YourFacadeProjectName")]
    

    注意替换成你实际的Facade项目名称,如果有强签名的话还要加上公钥,不过一般非强签名项目直接写名称就行。

  3. 在BusinessLogic里写内部DI扩展
    把仓储的注册逻辑封装在BusinessLogic项目的internal扩展方法里,只有友元程序集(Facade)能调用:

    internal static class BusinessLogicDiExtensions
    {
        internal static IServiceCollection AddBusinessLogicRepositories(this IServiceCollection services)
        {
            services.AddScoped<IProjectRepository, ProjectRepository>();
            // 其他仓储注册
            return services;
        }
    }
    
  4. 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;
        }
    }
    
  5. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 11:27:56