天天看點

(轉載非原創)Abp太重了?輕量化Abp架構

文章來源:https://blog.zhangchi.fun/posts/lightweightabp/

在進行架構的選型時,經常會聽到“***架構太重了”之類的聲音,比如“Abp太重了,不适合我們...”。事實上,Abp架構真的很重嗎?

架構的“輕”和“重”,我沒有在網上找到明确的定義,通過閱讀一些技術部落格,大緻可以把架構的“輕”和“重”通過以下幾個方面進行區分:

  • 所依賴程式集的數量
  • 所實作的功能的多少
  • 上手難度及易用性

“輕量級”的架構,大概指的是一個程式集依賴少且程式集檔案小、功能雖少但足夠滿足需求、上手容易使用簡單的架構;“重量級”的架構,大概指的是一個程式集依賴多且程式集檔案大、功能豐富但大多數用不到、上手困難且使用困難的架構。

這篇文章将從上述幾個方面來探索Abp是一個“輕量級”還是“重量級”的架構。

最小依賴

Abp開發了一些啟動模闆來為我們生成項目。啟動模闆采用了領域驅動設計的分層方案來建立項目層級,包括了展示層、應用層、領域層與基礎設施層。

(轉載非原創)Abp太重了?輕量化Abp架構

我們通常都會通過Abp CLI或Abp.io來建立類似上圖架構的項目。Abp為我們生成的項目,減少了我們初始化項目的工作量,開箱即用,是以将我們可能會使用的Nuget包預先引入到我們的項目中,也就給我們一種依賴項太多的感覺。

(轉載非原創)Abp太重了?輕量化Abp架構

從架構設計上來講,子產品化是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太重了?輕量化Abp架構

這裡我們實作了簡單的資料插入。可以看到,項目中并沒有使用複雜架構和複雜的領域驅動設計,僅引用并配置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中。