本文首发于个人博客 https://blog.zhangchi.fun/
在进行框架的选型时,经常会听到“***框架太重了”之类的声音,比如“Abp 太重了,不适合我们...”。事实上,Abp 框架真的很重吗?
框架的“轻”和“重”,我没有在网上找到明确的定义,通过阅读一些技术博客,大致可以把框架的“轻”和“重”通过以下几个方面进行区分:
所依赖程序集的数量
所实现的功能的多少
上手难度及易用性
“轻量级”的框架,大概指的是一个程序集依赖少且程序集文件小、功能虽少但足够满足需求、上手容易使用简单的框架;“重量级”的框架,大概指的是一个程序集依赖多且程序集文件大、功能丰富但大多数用不到、上手困难且使用困难的框架。
这篇文章将从上述几个方面来探索 Abp 是一个“轻量级”还是“重量级”的框架。
最小依赖
Abp 开发了一些启动模板来为我们生成项目。启动模板采用了领域驱动设计的分层方案来建立项目层级,包括了展示层、应用层、领域层与基础设施层。
我们通常都会通过Abp CLI或 Abp.io 来创建类似上图架构的项目。Abp 为我们生成的项目,减少了我们初始化项目的工作量,开箱即用,因此将我们可能会使用的 Nuget 包预先引入到我们的项目中,也就给我们一种依赖项太多的感觉。
从架构设计上来讲,模块化是 Abp 的核心;而从技术角度来看,依赖注入则是 Abp 实现众多功能的一个主要手段。只要了解 Abp 的模块化和依赖注入,我们就能够基于 Abp 框架来进行项目开发。
接下来将创建一个原生的ASP.NET Core Web API
项目,围绕模块化和依赖注入两个核心概念,来展示如何以最小依赖的方式使用 Abp。
通过 VS 或者 dotNet cli 新建一个原生的ASP.NET Core Web API
项目,命名为LightweightAbp
;
安装 Nuget 包Volo.Abp.Autofac
和Volo.Abp.AspNetCore.Mvc
;
将项目进行模块化:在项目根目录新建一个 Abp 模块代码文件LightweightAbpModule.cs
,并复制以下代码:
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpAspNetCoreMvcModule))]
public class LightweightAbpModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
}
}
复制代码
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpAspNetCoreMvcModule))]
public class LightweightAbpModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddControllers();
context.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "LightweightAbp", Version = "v1" });
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "LightweightAbp v1"));
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
复制代码
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddApplication<LightweightAbpModule>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
app.InitializeApplication();
}
}
复制代码
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseAutofac();
复制代码
至此项目的创建完成了。可以看到,仅仅依赖了Volo.Abp.Autofac
和Volo.Abp.AspNetCore.Mvc
两个 Nuget 包,即可利用 Abp 进行开发。若从所依赖 Nuget 包数量来评估框架的“轻”和“重”,那么 Abp 不可谓不轻。
功能按需使用
得益于模块化设计,Abp 将其所能提供的功能,划分并封装到了不同的模块中。要想使用 Abp 提供的某一功能,只需引入相关的 Nuget 包并依赖(DependsOn
)模块即可。
数据访问
要想实现数据访问功能,首先我们需要定义Entity
、DbContext
并配置数据库支持。在 Abp 的层次架构中,Entity
、Repository
属于领域层,Service
属于应用层,DbContext
则属于EntityFramework Core
模块,因此我们按需引入所需模块即可。
安装 Nuget 包Volo.Abp.Ddd.Application
、Volo.Abp.Ddd.Domain
和Volo.Abp.EntityFrameworkCore.Sqlite
;
在LightweightAbpModule
类中配置DependsOn
特性,将AbpDddApplicationModule
、AbpDddDomainModule
和AbpEntityFrameworkCoreSqliteModule
模块依赖到我们的项目模块中。
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpAspNetCoreMvcModule),
typeof(AbpDddApplicationModule),
typeof(AbpDddDomainModule),
typeof(AbpEntityFrameworkCoreSqliteModule))]
public class LightweightAbpModule : AbpModule
{ ... }
复制代码
using System;
using Volo.Abp.Domain.Entities;
namespace LightweightAbp
{
public class Book : Entity<Guid>
{
public string Name { get; set; }
}
}
复制代码
[ConnectionStringName("Default")]
public class LightweightAbpDbContext : AbpDbContext<LightweightAbpDbContext>
{
public LightweightAbpDbContext(DbContextOptions<LightweightAbpDbContext> options)
: base(options)
{ }
public DbSet<Book> Books { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Book>(b =>
{
b.ToTable(nameof(Books));
});
}
}
复制代码
public override void ConfigureServices(ServiceConfigurationContext context)
{
...
context.Services.AddAbpDbContext<LightweightAbpDbContext>(options =>
{
options.AddDefaultRepositories(includeAllEntities: true);
});
Configure<AbpDbContextOptions>(options =>
{
options.UseSqlite();
});
}
复制代码
{
...
"ConnectionStrings": {
"Default": "Data Source=LightweightAbp.db"
}
}
复制代码
dotnet ef migrations add InitialCreate
dotnet ef database update
复制代码
public interface IBookAppService
{
Task CreateAsync(string name);
}
复制代码
public class BookAppService : ApplicationService, IBookAppService
{
public IRepository<Book, Guid> Repository => LazyServiceProvider.LazyGetRequiredService<IRepository<Book, Guid>>();
public async Task<string> CreateAsync(string name)
{
var book = await Repository.InsertAsync(new Book()
{
Name = name
});
return book.Name;
}
}
复制代码
[ApiController]
[Route("[controller]")]
public class BookController : AbpController
{
private readonly IBookAppService _service;
public BookController(IBookAppService service)
{
_service = service;
}
[HttpGet]
public Task<string> CreateAsync(string name)
{
return _service.CreateAsync(name);
}
}
复制代码
这里我们实现了简单的数据插入。可以看到,项目中并没有使用复杂架构和复杂的领域驱动设计,仅引用并配置 Abp 模块,即可使用常规的 ASP.NET Core Web API
方式进行开发。
缓存
接下来我们将继续实现缓存功能。
public interface IBookAppService
{
Task<string> CreateAsync(string name);
Task<string[]> GetAllAsync();
}
复制代码
public class BookAppService : ApplicationService, IBookAppService
{
private readonly IRepository<Book, Guid> _repository;
private readonly IDistributedCache<string[]> _cache;
public BookAppService(
IRepository<Book, Guid> repository,
IDistributedCache<string[]> cache)
{
_repository = repository;
_cache = cache;
}
public async Task<string> CreateAsync(string name)
{ ... }
public async Task<string[]> GetAllAsync()
{
return await _cache.GetOrAddAsync(
"AllBooksName",
async () => await _repository.Select(b => b.Name).ToArrayAsync(),
() => new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
}
);
}
}
复制代码
public class BookController : AbpController
{
...
[HttpGet("all")]
public Task<string[]> GetAllAsync()
{
return _service.GetAllAsync();
}
}
复制代码
这里我们实现了缓存功能。显而易见,按需使用缓存功能所在的 Nuget 包及模块即可,并没有很多繁杂的操作。
众所周知,Abp 实现了相当多的功能,其中有些功能也许整个项目生命周期中都不会用到。得益于模块化的方式,我们可以只依赖我所需要的 Nuget 包和 Abp 模块。如果根据功能多少来评判框架的“轻”和“重”,我们按需依赖不同模块时 Abp 框架不可谓不轻。由此可见,一个框架的“轻”和“重”,有时还会取决于使用方式。
上手难度及易用性
学习一门新技术最好的起点便是官方文档,Abp 也是如此,Abp 的官方文档非常详尽介绍了各个功能。Abp 还为我们提供了启动模板,模板遵循了领域驱动设计的最佳实践来进行项目分层,并且为我们继承了很多项目中常用的功能模块。
对于初学者而言,面对一个复杂的分层架构及丰富的功能特性支持,一瞬间需要接受非常多的知识,因此会产生无从下手的感觉,进而得出一种上手难度高,框架很“重”的结论。
如果从另外一种角度来学习 Abp 的话,也许情况会有所不同。在本文之初,我便提出了 Abp 的核心是模块化及依赖注入的观点,当我们将入门的重点放在模块化和依赖注入上,那么会发现 Abp 是一个极易上手并且学习曲线很平缓的框架。正如上文我所进行的代码演示,如果感觉这个演示项目简单易学,那么就证明了我这一观点。
至于易用性,首先 Abp 实现的功能很全面,我们可以按需使用;其次,随着对 Abp 框架的逐步深入,会发现模块化的设计让我们的项目集成多种功能变得简单,并且随着项目的演进,Abp 的模块化给我们提供了轻易切换到微服务方案的能力;依赖注入系统让我们能够轻易的定制并替换 Abp 默认实现的功能。因此,我认为 Abp 是一个易于使用的框架。
总结
在这里我们从一个不同的角度来认识了 Abp 框架,显而易见,对于 Abp 来讲,是否太“重”,和我们对他的认知及使用方式有很大的关联。
项目示例代码将托管在Github中。
致谢
感谢 Abp 群(QQ 群:48039003)的群友们提供的热心帮助。
评论