博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
初识ABP vNext(11):聚合根、仓储、领域服务、应用服务、Blob储存
阅读量:4034 次
发布时间:2019-05-24

本文共 11871 字,大约阅读时间需要 39 分钟。

点击上方蓝字"小黑在哪里"关注我吧

  • 聚合根

  • 仓储

  • 领域服务

    • BLOB储存

  • 应用服务

  • 单元测试

  • 模块引用

前言

在前两节中介绍了ABP模块开发的基本步骤,试着实现了一个简单的文件管理模块;功能很简单,就是基于本地文件系统来完成文件的读写操作,数据也并没有保存到数据库,所以之前只简单使用了应用服务,并没有用到领域层。而在DDD中领域层是非常重要的一层,其中包含了实体,聚合根,领域服务,仓储等等,复杂的业务逻辑也应该在领域层来实现。本篇来完善一下文件管理模块,将文件记录保存到数据库,并使用ABP BLOB系统来完成文件的存储。

开始

聚合根

首先从实体模型开始,建立File实体。按照DDD的思路,这里的File应该是一个聚合根

\modules\file-management\src\Xhznl.FileManagement.Domain\Files\File.cs:

public class File : FullAuditedAggregateRoot
, IMultiTenant{    public virtual Guid? TenantId { get; protected set; }    [NotNull]    public virtual string FileName { get; protected set; }    [NotNull]    public virtual string BlobName { get; protected set; }    public virtual long ByteSize { get; protected set; }    protected File() { }    public File(Guid id, Guid? tenantId, [NotNull] string fileName, [NotNull] string blobName, long byteSize) : base(id)    {        TenantId = tenantId;        FileName = Check.NotNullOrWhiteSpace(fileName, nameof(fileName));        BlobName = Check.NotNullOrWhiteSpace(blobName, nameof(blobName));        ByteSize = byteSize;    }}

在DbContext中添加DbSet

\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\EntityFrameworkCore\IFileManagementDbContext.cs:

public interface IFileManagementDbContext : IEfCoreDbContext{    DbSet
 Files { get; }}

\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\EntityFrameworkCore\FileManagementDbContext.cs:

public class FileManagementDbContext : AbpDbContext
, IFileManagementDbContext{    public DbSet
 Files { get; set; }    ......}

配置实体

\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\EntityFrameworkCore\FileManagementDbContextModelCreatingExtensions.cs:

public static void ConfigureFileManagement(    this ModelBuilder builder,    Action
 optionsAction = null){    ......    builder.Entity
(b =>    {        //Configure table & schema name        b.ToTable(options.TablePrefix + "Files", options.Schema);        b.ConfigureByConvention();        //Properties        b.Property(q => q.FileName).IsRequired().HasMaxLength(FileConsts.MaxFileNameLength);        b.Property(q => q.BlobName).IsRequired().HasMaxLength(FileConsts.MaxBlobNameLength);        b.Property(q => q.ByteSize).IsRequired();    });}

仓储

ABP为每个聚合根或实体提供了 默认的通用(泛型)仓储 ,其中包含了标准的CRUD操作,注入IRepository<TEntity, TKey>即可使用。通常来说默认仓储就够用了,有特殊需求时也可以自定义仓储。

定义仓储接口

\modules\file-management\src\Xhznl.FileManagement.Domain\Files\IFileRepository.cs:

public interface IFileRepository : IRepository
{    Task
 FindByBlobNameAsync(string blobName);}

仓储实现

\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\Files\EfCoreFileRepository.cs:

public class EfCoreFileRepository : EfCoreRepository
, IFileRepository{    public EfCoreFileRepository(IDbContextProvider
 dbContextProvider) : base(dbContextProvider)    {    }    public async Task
 FindByBlobNameAsync(string blobName)    {        Check.NotNullOrWhiteSpace(blobName, nameof(blobName));        return await DbSet.FirstOrDefaultAsync(p => p.BlobName == blobName);    }}

注册仓储

\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\EntityFrameworkCore\FileManagementEntityFrameworkCoreModule.cs:

public class FileManagementEntityFrameworkCoreModule : AbpModule{    public override void ConfigureServices(ServiceConfigurationContext context)    {        context.Services.AddAbpDbContext
(options =>        {            options.AddRepository
();        });    }}

领域服务

定义领域服务接口

\modules\file-management\src\Xhznl.FileManagement.Domain\Files\IFileManager.cs:

public interface IFileManager : IDomainService{    Task
 FindByBlobNameAsync(string blobName);    Task
 CreateAsync(string fileName, byte[] bytes);    Task
 GetBlobAsync(string blobName);}

在实现领域服务之前,先来安装一下ABP Blob系统核心包,因为我要使用blob来存储文件,Volo.Abp.BlobStoring包是必不可少的。

BLOB储存

BLOB(binary large object):大型二进制对象;关于BLOB可以参考 BLOB 存储[1] ,这里不多介绍。

安装Volo.Abp.BlobStoring,在Domain项目目录下执行:abp add-package Volo.Abp.BlobStoring

Volo.Abp.BlobStoring是BLOB的核心包,它仅包含BLOB的一些基本抽象,想要BLOB系统正常工作,还需要为它配置一个提供程序;这个提供程序暂时不管,将来由模块的具体使用者去提供。这样的好处是模块不依赖特定存储提供程序,使用者可以随意的指定存储到阿里云,Azure,或者文件系统等等。。。

领域服务实现

\modules\file-management\src\Xhznl.FileManagement.Domain\Files\FileManager.cs:

public class FileManager : DomainService, IFileManager{    protected IFileRepository FileRepository { get; }    protected IBlobContainer BlobContainer { get; }    public FileManager(IFileRepository fileRepository, IBlobContainer blobContainer)    {        FileRepository = fileRepository;        BlobContainer = blobContainer;    }    public virtual async Task
 FindByBlobNameAsync(string blobName)    {        Check.NotNullOrWhiteSpace(blobName, nameof(blobName));        return await FileRepository.FindByBlobNameAsync(blobName);    }    public virtual async Task
 CreateAsync(string fileName, byte[] bytes)    {        Check.NotNullOrWhiteSpace(fileName, nameof(fileName));        var blobName = Guid.NewGuid().ToString("N");        var file = await FileRepository.InsertAsync(new File(GuidGenerator.Create(), CurrentTenant.Id, fileName, blobName, bytes.Length));        await BlobContainer.SaveAsync(blobName, bytes);        return file;    }    public virtual async Task
 GetBlobAsync(string blobName)    {        Check.NotNullOrWhiteSpace(blobName, nameof(blobName));        return await BlobContainer.GetAllBytesAsync(blobName);    }}

应用服务

接下来修改一下应用服务,应用服务通常没有太多业务逻辑,其调用领域服务来完成业务。

应用服务接口

\modules\file-management\src\Xhznl.FileManagement.Application.Contracts\Files\IFileAppService.cs:

public interface IFileAppService : IApplicationService{    Task
 FindByBlobNameAsync(string blobName);    Task
 CreateAsync(FileDto input);}

应用服务实现

\modules\file-management\src\Xhznl.FileManagement.Application\Files\FileAppService.cs:

public class FileAppService : FileManagementAppService, IFileAppService{    protected IFileManager FileManager { get; }    public FileAppService(IFileManager fileManager)    {        FileManager = fileManager;    }    public virtual async Task
 FindByBlobNameAsync(string blobName)    {        Check.NotNullOrWhiteSpace(blobName, nameof(blobName));        var file = await FileManager.FindByBlobNameAsync(blobName);        var bytes = await FileManager.GetBlobAsync(blobName);        return new FileDto        {            Bytes = bytes,            FileName = file.FileName        };    }    [Authorize]    public virtual async Task
 CreateAsync(FileDto input)    {        await CheckFile(input);        var file = await FileManager.CreateAsync(input.FileName, input.Bytes);        return file.BlobName;    }    protected virtual async Task CheckFile(FileDto input)    {        if (input.Bytes.IsNullOrEmpty())        {            throw new AbpValidationException("Bytes can not be null or empty!",                new List
                {                    new ValidationResult("Bytes can not be null or empty!", new[] {"Bytes"})                });        }        var allowedMaxFileSize = await SettingProvider.GetAsync
(FileManagementSettings.AllowedMaxFileSize);//kb        var allowedUploadFormats = (await SettingProvider.GetOrNullAsync(FileManagementSettings.AllowedUploadFormats))            ?.Split(",", StringSplitOptions.RemoveEmptyEntries);        if (input.Bytes.Length > allowedMaxFileSize * 1024)        {            throw new UserFriendlyException(L["FileManagement.ExceedsTheMaximumSize", allowedMaxFileSize]);        }        if (allowedUploadFormats == null || !allowedUploadFormats.Contains(Path.GetExtension(input.FileName)))        {            throw new UserFriendlyException(L["FileManagement.NotValidFormat"]);        }    }}

API控制器

最后记得将服务接口暴露出去,我这里是自己编写Controller,你也可以使用ABP的自动API控制器来完成,请参考 自动API控制器[2]

\modules\file-management\src\Xhznl.FileManagement.HttpApi\Files\FileController.cs:

[RemoteService][Route("api/file-management/files")]public class FileController : FileManagementController{    protected IFileAppService FileAppService { get; }    public FileController(IFileAppService fileAppService)    {        FileAppService = fileAppService;    }    [HttpGet]    [Route("{blobName}")]    public virtual async Task
 GetAsync(string blobName)    {        var fileDto = await FileAppService.FindByBlobNameAsync(blobName);        return File(fileDto.Bytes, MimeTypes.GetByExtension(Path.GetExtension(fileDto.FileName)));    }    [HttpPost]    [Route("upload")]    [Authorize]    public virtual async Task
 CreateAsync(IFormFile file)    {        if (file == null)        {            throw new UserFriendlyException("No file found!");        }        var bytes = await file.GetAllBytesAsync();        var result = await FileAppService.CreateAsync(new FileDto()        {            Bytes = bytes,            FileName = file.FileName        });        return Json(result);    }}

单元测试

针对以上内容做一个简单的测试,首先为Blob系统配置一个提供程序。

我这里使用最简单的文件系统来储存,所以需要安装Volo.Abp.BlobStoring.FileSystem。在Application.Tests项目目录下执行:abp add-package Volo.Abp.BlobStoring.FileSystem

配置默认容器

\modules\file-management\test\Xhznl.FileManagement.Application.Tests\FileManagementApplicationTestModule.cs:

[DependsOn(    typeof(FileManagementApplicationModule),    typeof(FileManagementDomainTestModule),    typeof(AbpBlobStoringFileSystemModule)    )]public class FileManagementApplicationTestModule : AbpModule{    public override void ConfigureServices(ServiceConfigurationContext context)    {        Configure
(options =>        {            options.Containers.ConfigureDefault(container =>            {                container.UseFileSystem(fileSystem =>                {                    fileSystem.BasePath = "D:\\my-files";                });            });        });        base.ConfigureServices(context);    }}

测试用例

\modules\file-management\test\Xhznl.FileManagement.Application.Tests\Files\FileAppService_Tests.cs:

public class FileAppService_Tests : FileManagementApplicationTestBase{    private readonly IFileAppService _fileAppService;    public FileAppService_Tests()    {        _fileAppService = GetRequiredService
();    }    [Fact]    public async Task Create_FindByBlobName_Test()    {        var blobName = await _fileAppService.CreateAsync(new FileDto()        {            FileName = "微信图片_20200813165555.jpg",            Bytes = await System.IO.File.ReadAllBytesAsync(@"D:\WorkSpace\WorkFiles\杂项\图片\微信图片_20200813165555.jpg")        });        blobName.ShouldNotBeEmpty();        var fileDto = await _fileAppService.FindByBlobNameAsync(blobName);        fileDto.ShouldNotBeNull();        fileDto.FileName.ShouldBe("微信图片_20200813165555.jpg");    }}

运行测试

测试通过,blob也已经存入D:\my-files:

模块引用

下面回到主项目,前面的章节中已经介绍过,模块的引用依赖都已经添加完成,下面就直接从数据库迁移开始。

\src\Xhznl.HelloAbp.EntityFrameworkCore.DbMigrations\EntityFrameworkCore\HelloAbpMigrationsDbContext.cs:

public class HelloAbpMigrationsDbContext : AbpDbContext
{    public HelloAbpMigrationsDbContext(DbContextOptions
 options)        : base(options)    {    }    protected override void OnModelCreating(ModelBuilder builder)    {        ......                    builder.ConfigureFileManagement();                ......    }}

打开程序包管理器控制台,执行以下命令:

Add-Migration "Added_FileManagement"

Update-Database

此时数据库已经生成了File表:

还有记得在HttpApi.Host项目配置你想要的blob提供程序。

最后结合前端测试一下吧:

最后

以上就是本人所理解的abp模块开发一个相对完整的流程,还有些概念后面再做补充。因为这个例子比较简单,文中有些环节是不必要的,需要结合实际情况去取舍。代码地址:https://github.com/xiajingren/HelloAbp

参考资料

[1]

BLOB 存储: https://docs.abp.io/zh-Hans/abp/latest/Blob-Storing

[2]

自动API控制器: https://docs.abp.io/zh-Hans/abp/latest/API/Auto-API-Controllers

如果本文对您有用,

不妨点个“”或者转发朋友圈支持一下

转载地址:http://jykdi.baihongyu.com/

你可能感兴趣的文章
如何用好碎片化时间,让思维更有效率?
查看>>
第一性原理:戳中问题本质的人是怎么思考的?
查看>>
No.147 - LeetCode1108
查看>>
No.148 - LeetCode771
查看>>
No.174 - LeetCode1305 - 合并两个搜索树
查看>>
No.175 - LeetCode1306
查看>>
No.176 - LeetCode1309
查看>>
No.182 - LeetCode1325 - C指针的魅力
查看>>
mac:移动python包路径
查看>>
mysql:sql create database新建utf8mb4 数据库
查看>>
mysql:sql alter database修改数据库字符集
查看>>
mysql:sql drop table (删除表)
查看>>
mysql:sql truncate (清除表数据)
查看>>
scrapy:xpath string(.)非常注意问题
查看>>
yuv to rgb 转换失败呀。天呀。谁来帮帮我呀。
查看>>
yuv420 format
查看>>
YUV420只绘制Y通道
查看>>
yuv420 还原为RGB图像
查看>>
LED恒流驱动芯片
查看>>
驱动TFT要SDRAM做为显示缓存
查看>>