Ultimate ASP.NET CORE 6.0 Web API --- 读书笔记(3)

2022/6/17 1:20:06

本文主要是介绍Ultimate ASP.NET CORE 6.0 Web API --- 读书笔记(3),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Onion Architecture Implementation

本文内容来自书籍: Marinko Spasojevic - Ultimate ASP.NET Core Web API - From Zero To Six-Figure Backend Developer (2nd edition)

这章是说的洋葱架构实现,学习如何创建不同的层,以此分割应用的不同部分。

对数据服务,我们需要创建Model,使用code first的方法将Model转换成数据库表

我们将会创建一个Repository层作为数据访问层,当创建这样一个抽象层后,我们会把业务逻辑和数据访问分割开。

这样我们的业务逻辑代码会变得更加干净,然后将所有的业务逻辑划分在Service层,将外部的访问划分在presentation

关于洋葱的切分有不同的方法,这里我们将它分割为四个层次

  • Domain Layer
  • Service Layer
  • Infrastructure Layer
  • Presentation Layer

然后PresentationInfrastructure在层次结构上是在同一个level

下面看看更多的细节,看看为什么要这样做

3.1.1 Advantages of the Onion Architecture

所有层次间的交互时严格按照接口的定义

依赖关系的流向时指向洋葱的中心的

在整个项目中使用依赖倒置,依赖抽象而不是实现,可以允许我们在运行时切换实现

在编译时依赖抽象,在运行时提供实现

当所有东西都是依赖抽象的,这样会使得整个架构变得容易测试

因为我们可以通过Moq来替换抽象的实现,这样我们在编写业务逻辑的时候,就不需要依赖任何实现的细节

如果需要任何来自外部的服务,只需要创建一个接口,然后consume,不需要担心它的实现

3.1.2 Flow of Dependencies

在洋葱里面,层次越深,所需要的依赖越少

Domain layer没有任何直接的依赖外部的层次,它是个孤岛一样的

洋葱的低层次定义接口,高层次的洋葱实现这些接口

使用这种方法,我们就可以将所有的业务逻辑封装在ServiceDomain中,而不需要知道任何实现的细节

Service中,我们只知道定义在Domain中的接口

3.2 Creating Models

根据之前的理论,创建一个库Entities,然后在里面创建一个文件夹Models,它是包含了所有的模型

Entities代表了EF Core这个框架用来映射到数据库的所有模型

然后再创建一个新的库Repository,它是数据库的上下文和repository的实现

Repository是关于数据库访问的实现,它需要引用EntityFrameworkCoreEntities

然后需要创建一个RepositoryContext继承EntityFrameworkCoreDbContext,用来代表访问数据库的上下文

在主项目中,需要配置数据库连接的信息,在appsettings.json中,而且主项目需要引用Repository

 "ConnectionStrings": {
 "sqlConnection": "server=.; database=CompanyEmployee; Integrated Security=true"
 }

然后在Repository项目中,需要创建关于数据库上下文构建的工厂类

public class RepositoryContextFactory : IDesignTimeDbContextFactory<RepositoryContext>
{
    public RepositoryContext CreateDbContext(string[] args)
    {
        var configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json")
            .Build();
        var builder = new DbContextOptionsBuilder<RepositoryContext>()
            .UseSqlServer(configuration.GetConnectionString("sqlConnection"));
        return new RepositoryContext(builder.Options);
    } }

3.4 Migration and Initial Data Seed

迁移是标准的创建或更新数据库的过程,当我们创建Model之后,我们就可以将Model映射到真实的数据库中

首先修改之前的创建数据库上下文的方法

public class RepositoryContextFactory : IDesignTimeDbContextFactory<RepositoryContext>
{
    public RepositoryContext CreateDbContext(string[] args)
    {
        var configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json")
            .Build();
        var builder = new DbContextOptionsBuilder<RepositoryContext>()
            .UseSqlServer(configuration.GetConnectionString("sqlConnection"),
                b => b.MigrationsAssembly("CompanyEmployees"));
        return new RepositoryContext(builder.Options);
    } }

修改这个是因为,我们的迁移程序集不在主程序,而是在Repository

在迁移之前,需要引用这个库Microsoft.EntityFrameworkCore.Tools,然后执行迁移并更新数据库

在创建数据库之后,我们一些初始数据,在Repository中创建一个文件夹Configuration

创建一个类CompanyConfiguration用以初始化

public class CompanyConfiguration : IEntityTypeConfiguration<Company>
{
    public void Configure(EntityTypeBuilder<Company> builder)
    {
        builder.HasData
        (
            new Company
            {
                Id = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870"),
                Name = "IT_Solutions Ltd",
                Address = "583 Wall Dr. Gwynn Oak, MD 21207",
                Country = "USA"
            },
            new Company
            {
                Id = new Guid("3d490a70-94ce-4d15-9494-5248280c2ce3"),
                Name = "Admin_Solutions Ltd",
                Address = "312 Forest Avenue, BF 923",
                Country = "USA"
            }
        );
    } 
}

然后修改RepositoryContext

public class RepositoryContext: DbContext
{
    public RepositoryContext(DbContextOptions options)
    : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new CompanyConfiguration());
        modelBuilder.ApplyConfiguration(new EmployeeConfiguration());
    }

    public DbSet<Company> Companies { get; set; }
    public DbSet<Employee> Employees { get; set; }
}

然后再迁移和更新数据库

3.5 Repository Pattern Logic

我们需要创建通用的repository,用以提供CRUD方法

然后对repository做一个包装,用作服务注册到IoC中

最后,我们将实例化这个repository,并且在任何controllers中将需要的repository注入

  1. 首先,在Contracts项目中,为repository创建接口
public interface IRepositoryBase<T> {
    IQueryable<T> FindAll(bool trackChanges);
    IQueryable<T> FindByCondition(Expression<Func<T, bool>> expression, 
    bool trackChanges);
    void Create(T entity);
    void Update(T entity);
    void Delete(T entity);
}
  1. Repository引用Contracts,然后创建一个抽象类RepositoryBase用作对IRepositoryBase的实现
public abstract class RepositoryBase<T> : IRepositoryBase<T> where T : class
{
    protected RepositoryContext RepositoryContext;
    public RepositoryBase(RepositoryContext repositoryContext) 
    => RepositoryContext = repositoryContext;
    
    public IQueryable<T> FindAll(bool trackChanges) =>
        !trackChanges 
            ? RepositoryContext.Set<T>().AsNoTracking() 
            : RepositoryContext.Set<T>();

    public IQueryable<T> FindByCondition(Expression<Func<T, bool>> expression,
    bool trackChanges) =>
        !trackChanges 
            ? RepositoryContext.Set<T>()
                .Where(expression)
                .AsNoTracking() 
            : RepositoryContext.Set<T>()
                .Where(expression); 

    public void Create(T entity) => RepositoryContext.Set<T>().Add(entity);
    public void Update(T entity) => RepositoryContext.Set<T>().Update(entity);
    public void Delete(T entity) => RepositoryContext.Set<T>().Remove(entity);
}

3.6 Repository User Interfaces and Classes

现在创建实际使用的类,它是继承自RepositoryBase,而且,它需要有特定于模型的接口,这个接口可能有其他特殊的方法

这样,我们就可以将公共的逻辑和特定于模型的逻辑切割分开

  1. 首先在Contracts中建立接口
namespace Contracts
{
    public interface ICompanyRepository
    {
    } 
}

namespace Contracts
{
    public interface IEmployeeRepository
    {
    } 
}
  1. 然后在Repository中,创建User类
public class CompanyRepository : RepositoryBase<Company>, ICompanyRepository
{
    public CompanyRepository(RepositoryContext repositoryContext) 
    : base(repositoryContext)
    {
    } 
}

3.7 Creating a Repository Manager

从API中获取由多个repository组成的结果,是非常正常的逻辑,所以有时候需要实例化多个repository来从数据库中拉取数据

如果只有两个或者很少的repository,当然没有问题,但是如果有非常多的不同repository的时候,就会变得非常复杂

所以我们需要构建一个Repository Manager,用它来帮我们实例化repository,并且注册到IoC中

  1. 首先,在Contract中创建接口
public interface IRepositoryManager
{
    ICompanyRepository Company { get; }
    IEmployeeRepository Employee { get; }
    void Save();
}
  1. 然后在Repository中,实现这个接口
public sealed class RepositoryManager : IRepositoryManager
{
    private readonly RepositoryContext _repositoryContext;
    private readonly Lazy<ICompanyRepository> _companyRepository;
    private readonly Lazy<IEmployeeRepository> _employeeRepository;

    public RepositoryManager(RepositoryContext repositoryContext)
    {
        _repositoryContext = repositoryContext;
        _companyRepository = new Lazy<ICompanyRepository>(() => new
        CompanyRepository(repositoryContext));
        _employeeRepository = new Lazy<IEmployeeRepository>(() => new
        EmployeeRepository(repositoryContext));
    }

    public ICompanyRepository Company => _companyRepository.Value;

    public IEmployeeRepository Employee => _employeeRepository.Value;

    public void Save() => _repositoryContext.SaveChanges();
}
  1. 最后需要将这个RepositoryManager注册到主项目中
public static void ConfigureRepositoryManager(this IServiceCollection services) =>
 services.AddScoped<IRepositoryManager, RepositoryManager>();

// Program.cs
builder.Services.ConfigureRepositoryManager();

3.8 Adding a Service Layer

Service是在DomainContractsDomain的一部分)的上一层,

所以Service会引用Domain

Service Layer会切分为两个项目Service.ContractsService

Service.Contracts是用于定义服务接口,以此封装主要的业务逻辑

然后会有三个接口

  • ICompanyService
  • IEmployeeService
  • IServiceManager

其实这几个接口和Repository中使用的模式是一样的,接下来就是在Service中实现这几个接口

internal sealed class CompanyService : ICompanyService
{
    private readonly IRepositoryManager _repository;
    private readonly ILoggerManager _logger;

    public CompanyService(IRepositoryManager repository, ILoggerManager logger)
    {
        _repository = repository;
        _logger = logger;
    } 
}
public sealed class ServiceManager : IServiceManager
{
    private readonly Lazy<ICompanyService> _companyService;
    private readonly Lazy<IEmployeeService> _employeeService;
    public ServiceManager(IRepositoryManager repositoryManager, ILoggerManager logger)
    {
        _companyService = new Lazy<ICompanyService>(() => new 
            CompanyService(repositoryManager, logger));

        _employeeService = new Lazy<IEmployeeService>(() => new
            EmployeeService(repositoryManager, logger));
    }

    public ICompanyService CompanyService => _companyService.Value;
    public IEmployeeService EmployeeService => _employeeService.Value;
}

然后在主项目中引用Service,并注册服务

public static void ConfigureServiceManager(this IServiceCollection services) =>
    services.AddScoped<IServiceManager, ServiceManager>();

// Program.cs
builder.Services.ConfigureServiceManager();

3.9 Registering RepositoryContext at a Runtime

在实现了IDesignTimeDbContextFactory接口的RepositoryContextFactory中,我们可以在设计时就注册了RepositoryContext,这可以让我们在迁移的时候,在别的项目找到RepositoryContext并执行

然后我们需要修改数据库的注册方法

public static void ConfigureSqlContext(this IServiceCollection services, 
IConfiguration configuration) =>
    services.AddDbContext<RepositoryContext>(opts =>
        opts.UseSqlServer(configuration.GetConnectionString("sqlConnection")));

不需要MigrationAssembly这个方法了

项目到了后面,在迁移数据库的时候,还是出现了错误提示,还是需要添加MigrationAssembly这个方法来指定



这篇关于Ultimate ASP.NET CORE 6.0 Web API --- 读书笔记(3)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程