初识ABP vNext(11):聚合根、仓储、领域服务、应用服务、Blob储存
點擊上方藍字"小黑在哪里"關注我吧
聚合根
倉儲
領域服務
BLOB儲存
應用服務
單元測試
模塊引用
前言
在前兩節中介紹了ABP模塊開發的基本步驟,試著實現了一個簡單的文件管理模塊;功能很簡單,就是基于本地文件系統來完成文件的讀寫操作,數據也并沒有保存到數據庫,所以之前只簡單使用了應用服務,并沒有用到領域層。而在DDD中領域層是非常重要的一層,其中包含了實體,聚合根,領域服務,倉儲等等,復雜的業務邏輯也應該在領域層來實現。本篇來完善一下文件管理模塊,將文件記錄保存到數據庫,并使用ABP BLOB系統來完成文件的存儲。
開始
聚合根
首先從實體模型開始,建立File實體。按照DDD的思路,這里的File應該是一個聚合根。
\modules\file-management\src\Xhznl.FileManagement.Domain\Files\File.cs:
public?class?File?:?FullAuditedAggregateRoot<Guid>,?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<File>?Files?{?get;?} }\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\EntityFrameworkCore\FileManagementDbContext.cs:
public?class?FileManagementDbContext?:?AbpDbContext<FileManagementDbContext>,?IFileManagementDbContext {public?DbSet<File>?Files?{?get;?set;?}...... }配置實體
\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\EntityFrameworkCore\FileManagementDbContextModelCreatingExtensions.cs:
public?static?void?ConfigureFileManagement(this?ModelBuilder?builder,Action<FileManagementModelBuilderConfigurationOptions>?optionsAction?=?null) {......builder.Entity<File>(b?=>{//Configure?table?&?schema?nameb.ToTable(options.TablePrefix?+?"Files",?options.Schema);b.ConfigureByConvention();//Propertiesb.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<File,?Guid> {Task<File>?FindByBlobNameAsync(string?blobName); }倉儲實現
\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\Files\EfCoreFileRepository.cs:
public?class?EfCoreFileRepository?:?EfCoreRepository<IFileManagementDbContext,?File,?Guid>,?IFileRepository {public?EfCoreFileRepository(IDbContextProvider<IFileManagementDbContext>?dbContextProvider)?:?base(dbContextProvider){}public?async?Task<File>?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<FileManagementDbContext>(options?=>{options.AddRepository<File,?EfCoreFileRepository>();});} }領域服務
定義領域服務接口
\modules\file-management\src\Xhznl.FileManagement.Domain\Files\IFileManager.cs:
public?interface?IFileManager?:?IDomainService {Task<File>?FindByBlobNameAsync(string?blobName);Task<File>?CreateAsync(string?fileName,?byte[]?bytes);Task<byte[]>?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<File>?FindByBlobNameAsync(string?blobName){Check.NotNullOrWhiteSpace(blobName,?nameof(blobName));return?await?FileRepository.FindByBlobNameAsync(blobName);}public?virtual?async?Task<File>?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<byte[]>?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<FileDto>?FindByBlobNameAsync(string?blobName);Task<string>?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<FileDto>?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<string>?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<ValidationResult>{new?ValidationResult("Bytes?can?not?be?null?or?empty!",?new[]?{"Bytes"})});}var?allowedMaxFileSize?=?await?SettingProvider.GetAsync<int>(FileManagementSettings.AllowedMaxFileSize);//kbvar?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<FileResult>?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<JsonResult>?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<AbpBlobStoringOptions>(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<IFileAppService>();}[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<HelloAbpMigrationsDbContext> {public?HelloAbpMigrationsDbContext(DbContextOptions<HelloAbpMigrationsDbContext>?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
如果本文對您有用,
不妨點個“在看”或者轉發朋友圈支持一下
總結
以上是生活随笔為你收集整理的初识ABP vNext(11):聚合根、仓储、领域服务、应用服务、Blob储存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: chrome禁止三方cookie,网站登
- 下一篇: RabbitMq如何确保消息不丢失