本文首发于个人博客 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 InitialCreatedotnet 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)的群友们提供的热心帮助。
评论