在進行框架的選型時,經常會聽到“***框架太重了”之類的聲音,比如“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)
{
}
}
- 將
Startup
中的程式碼調整到LightweightAbpModule
中,程式碼如下:
[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();
});
}
}
- 更改
Startup
中的程式碼以使用Abp的模組化系統:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddApplication<LightweightAbpModule>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
app.InitializeApplication();
}
}
- 更改
Program
的CreateHostBuilder
方法以使用Abp的依賴注入系統(基於Autofac):
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseAutofac();
- 將專案生成的
WeatherForecastController
基類ControllerBase
更改為AbpController
。 - 按
F5
執行。
至此專案的建立完成了。可以看到,僅僅依賴了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
{ ... }
- 然後建立實體
Book
及資料庫上下文LightweightAbpDbContext
:
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));
});
}
}
- 在
LightweightAbpModule
的ConfigureServices
方法中配置資料庫訪問:
public override void ConfigureServices(ServiceConfigurationContext context)
{
...
context.Services.AddAbpDbContext<LightweightAbpDbContext>(options =>
{
options.AddDefaultRepositories(includeAllEntities: true);
});
Configure<AbpDbContextOptions>(options =>
{
options.UseSqlite();
});
}
- 在
appsettings.json
中配置資料庫連線字串
{
...
"ConnectionStrings": {
"Default": "Data Source=LightweightAbp.db"
}
}
- 安裝Nuget包"Microsoft.EntityFrameworkCore.Tools",並在在專案根目錄下開啟命令列工具,依次執行以下命令進行資料遷移和資料庫更新:
dotnet ef migrations add InitialCreate
dotnet ef database update
- 建立
IBookAppService
及BookAppService
:
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;
}
}
- 在資料夾Controllers中建立
BookController
:
[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);
}
}
- F5以除錯模式執行即可在Swagger頁面上插入資料:
這裡我們實現了簡單的資料插入。可以看到,專案中並沒有使用複雜架構和複雜的領域驅動設計,僅引用並配置Abp模組,即可使用常規的 ASP.NET Core Web API
方式進行開發。
快取
接下來我們將繼續實現快取功能。
- 引用Nuget包
Volo.Abp.Caching
並向LightweightAbpModule
新增AbpCachingModule
模組依賴; - 修改
IBookAppService
及BookAppService
實現GetAllAsync
方法:
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)
}
);
}
}
- 修改
BookAppService
實現GetAllAsync
API介面:
public class BookController : AbpController
{
...
[HttpGet("all")]
public Task<string[]> GetAllAsync()
{
return _service.GetAllAsync();
}
}
- F5以除錯方式執行,即可呼叫實現了快取功能的
GetAllAsync
介面。
這裡我們實現了快取功能。顯而易見,按需使用快取功能所在的Nuget包及模組即可,並沒有很多繁雜的操作。
眾所周知,Abp實現了相當多的功能,其中有些功能也許整個專案生命週期中都不會用到。得益於模組化的方式,我們可以只依賴我所需要的Nuget包和Abp模組。如果根據功能多少來評判框架的“輕”和“重”,我們按需依賴不同模組時Abp框架不可謂不輕。由此可見,一個框架的“輕”和“重”,有時還會取決於使用方式。
上手難度及易用性
學習一門新技術最好的起點便是官方文件,Abp也是如此,Abp的官方文件非常詳盡介紹了各個功能。Abp還為我們提供了啟動模板,模板遵循了領域驅動設計的最佳實踐來進行專案分層,並且為我們繼承了很多專案中常用的功能模組。
對於初學者而言,面對一個複雜的分層架構及豐富的功能特性支援,一瞬間需要接受非常多的知識,因此會產生無從下手的感覺,進而得出一種上手難度高,框架很“重”的結論。
如果從另外一種角度來學習Abp的話,也許情況會有所不同。在本文之初,我便提出了Abp的核心是模組化及依賴注入的觀點,當我們將入門的重點放在模組化和依賴注入上,那麼會發現Abp是一個極易上手並且學習曲線很平緩的框架。正如上文我所進行的程式碼演示,如果感覺這個演示專案簡單易學,那麼就證明了我這一觀點。
至於易用性,首先Abp實現的功能很全面,我們可以按需使用;其次,隨著對Abp框架的逐步深入,會發現模組化的設計讓我們的專案整合多種功能變得簡單,並且隨著專案的演進,Abp的模組化給我們提供了輕易切換到微服務方案的能力;依賴注入系統讓我們能夠輕易的定製並替換Abp預設實現的功能。因此,我認為Abp是一個易於使用的框架。
總結
在這裡我們從一個不同的角度來認識了Abp框架,顯而易見,對於Abp來講,是否太“重”,和我們對他的認知及使用方式有很大的關聯。
專案示例程式碼將託管在Github中。
致謝
感謝Abp群(QQ群:48039003)的群友們提供的熱心幫助。