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
然后Presentation和Infrastructure在层次结构上是在同一个level的
下面看看更多的细节,看看为什么要这样做
3.1.1 Advantages of the Onion Architecture
所有层次间的交互时严格按照接口的定义
依赖关系的流向时指向洋葱的中心的
在整个项目中使用依赖倒置,依赖抽象而不是实现,可以允许我们在运行时切换实现
在编译时依赖抽象,在运行时提供实现
当所有东西都是依赖抽象的,这样会使得整个架构变得容易测试
因为我们可以通过Moq来替换抽象的实现,这样我们在编写业务逻辑的时候,就不需要依赖任何实现的细节
如果需要任何来自外部的服务,只需要创建一个接口,然后consume,不需要担心它的实现
3.1.2 Flow of Dependencies
在洋葱里面,层次越深,所需要的依赖越少
Domain layer没有任何直接的依赖外部的层次,它是个孤岛一样的
洋葱的低层次定义接口,高层次的洋葱实现这些接口
使用这种方法,我们就可以将所有的业务逻辑封装在Service和Domain中,而不需要知道任何实现的细节
在Service中,我们只知道定义在Domain中的接口
3.2 Creating Models
根据之前的理论,创建一个库Entities,然后在里面创建一个文件夹Models,它是包含了所有的模型
Entities代表了EF Core这个框架用来映射到数据库的所有模型
然后再创建一个新的库Repository,它是数据库的上下文和repository的实现
Repository是关于数据库访问的实现,它需要引用EntityFrameworkCore和Entities
然后需要创建一个RepositoryContext继承EntityFrameworkCore的DbContext,用来代表访问数据库的上下文
在主项目中,需要配置数据库连接的信息,在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注入
- 首先,在
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);
}
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,而且,它需要有特定于模型的接口,这个接口可能有其他特殊的方法
这样,我们就可以将公共的逻辑和特定于模型的逻辑切割分开
- 首先在
Contracts中建立接口
namespace Contracts
{
public interface ICompanyRepository
{
}
}
namespace Contracts
{
public interface IEmployeeRepository
{
}
}
- 然后在
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中
- 首先,在
Contract中创建接口
public interface IRepositoryManager
{
ICompanyRepository Company { get; }
IEmployeeRepository Employee { get; }
void Save();
}
- 然后在
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();
}
- 最后需要将这个
RepositoryManager注册到主项目中
public static void ConfigureRepositoryManager(this IServiceCollection services) =>
services.AddScoped<IRepositoryManager, RepositoryManager>();
// Program.cs
builder.Services.ConfigureRepositoryManager();
3.8 Adding a Service Layer
Service是在Domain(Contracts是Domain的一部分)的上一层,
所以Service会引用Domain
Service Layer会切分为两个项目Service.Contracts和Service
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这个方法来指定

浙公网安备 33010602011771号