五、Abp vNext 基础篇丨博客聚合功能

五、Abp vNext 基础篇丨博客聚合功能介绍业务篇章先从客户端开始写,另外补充一下我给项目起名的时候没多想起的太随意了,结果后面有些地方命名冲突了需要通过手动using不过问题不大。开工应用层根据第三章分层架构里面讲到的现在我们模型

大家好,又见面了,我是你们的朋友全栈君。

介绍

业务篇章先从客户端开始写,另外补充一下我给项目起名的时候没多想起的太随意了,结果后面有些地方命名冲突了需要通过手动using不过问题不大。

开工

应用层

根据第三章分层架构里面讲到的现在我们模型已经创建好了,下一步应该是去Application.Contracts层创建我们的业务接口和Dto.

Blog业务接口


    public interface IBlogAppService : IApplicationService
    {
        Task<ListResultDto<BlogDto>> GetListAsync();

        Task<BlogDto> GetByShortNameAsync(string shortName);

        Task<BlogDto> GetAsync(Guid id);
    }


    public class BlogDto : FullAuditedEntityDto<Guid>
    {
        public string Name { get; set; }

        public string ShortName { get; set; }

        public string Description { get; set; }
    }

接口写完之后,我们去Application层实现 Application.Contracts 中定义的服务接⼝,应⽤服务是⽆状态服务,实现应⽤程序⽤例。⼀个应⽤服务通常使⽤领域对象实现⽤例,获取或返回数 据传输对象DTOs,被展示层调⽤。

应⽤服务通⽤原则:

  • 实现特定⽤例的应⽤逻辑,不能在应⽤服务中实现领域逻辑(需要理清应⽤逻辑和领域逻辑⼆者的 区别)。
  • 应⽤服务⽅法不能返回实体,因为这样会打破领域层的封装性,始终只返回DTO。

大家先看下面的代码有什么问题

public class BlogAppService : CoreAppService, IBlogAppService
    {
        private readonly IRepository<Blog> _blogRepository;

        public BlogAppService(IRepository<Blog> blogRepository)
        {
            _blogRepository = blogRepository;
        }
        public async Task<ListResultDto<BlogDto>> GetListAsync()
        {
            var blogs = await _blogRepository.GetListAsync();

            return new ListResultDto<BlogDto>(
                ObjectMapper.Map<List<Blog>, List<BlogDto>>(blogs)
            );
        }

        public async Task<BlogDto> GetByShortNameAsync(string shortName)
        {
            Check.NotNullOrWhiteSpace(shortName, nameof(shortName));

            var blog =  await _blogRepository.GetAsync(x=>x.ShortName == shortName);

            if (blog == null)
            {
                throw new EntityNotFoundException(typeof(Blog), shortName);
            }

            return ObjectMapper.Map<Blog, BlogDto>(blog);
        }

        public async Task<BlogDto> GetAsync(Guid id)
        {
            var blog = await _blogRepository.GetAsync(x=>x.Id == id);

            return ObjectMapper.Map<Blog, BlogDto>(blog);
        }
    }

错误:上面代码违反了应用层原则将特定⽤例的应⽤逻辑写在了应⽤服务层。

仓储

解决上面的问题就要用到仓储,ABP默认提供的泛型仓储无法满足业务需要的时候就需要我们自定义仓储,仓储应该只针对聚合根,⽽不是所有实体。因为⼦集合实体(聚合)应该通过聚合根访问。

仓储定义写在领域层,仓储实现写在基础层,参照第三章:ABP项目分层解析关于数据库独⽴性原则的讨论

仓储的通⽤原则

  • 在领域层中定义仓储接⼝,在基础层中实现仓储接⼝(⽐如: EntityFrameworkCore 项⽬ 或 MongoDB 项⽬)
  • 仓储不包含业务逻辑,专注数据处理。
  • 仓储接⼝应该保持 数据提供程序/ORM 独⽴性。举个例⼦,仓储接⼝定义的⽅法不能返回 DbSet 对象,因为该对象由 EF Core 提供,如果使⽤ MongoDB 数据库则⽆法实现该接⼝。
  • 为聚合根创建对应仓储,⽽不是所有实体。因为⼦集合实体(聚合)应该通过聚合根访问。

项目结构

    public interface IBlogRepository : IBasicRepository<Blog, Guid>
    {
        Task<Blog> FindByShortNameAsync(string shortName, CancellationToken cancellationToken = default);
    }



    public class EfCoreBlogRepository : EfCoreRepository<CoreDbContext, Blog, Guid>, IBlogRepository
    {
        public EfCoreBlogRepository(IDbContextProvider<CoreDbContext> dbContextProvider)
            : base(dbContextProvider)
        {

        }

        public async Task<Blog> FindByShortNameAsync(string shortName, CancellationToken cancellationToken = default)
        {
            return await (await GetDbSetAsync()).FirstOrDefaultAsync(p => p.ShortName == shortName, GetCancellationToken(cancellationToken));
        }
    }



    public class BlogAppService : CoreAppService, IBlogAppService
    {
        private readonly IBlogRepository _blogRepository;

        public BlogAppService(IBlogRepository blogRepository)
        {
            _blogRepository = blogRepository;
        }
        public async Task<ListResultDto<BlogDto>> GetListAsync()
        {
            var blogs = await _blogRepository.GetListAsync();

            return new ListResultDto<BlogDto>(
                ObjectMapper.Map<List<Blog>, List<BlogDto>>(blogs)
            );
        }

        public async Task<BlogDto> GetByShortNameAsync(string shortName)
        {
            Check.NotNullOrWhiteSpace(shortName, nameof(shortName));

            var blog = await _blogRepository.FindByShortNameAsync(shortName);

            if (blog == null)
            {
                throw new EntityNotFoundException(typeof(Blog), shortName);
            }

            return ObjectMapper.Map<Blog, BlogDto>(blog);
        }

        public async Task<BlogDto> GetAsync(Guid id)
        {
            var blog = await _blogRepository.GetAsync(id);

            return ObjectMapper.Map<Blog, BlogDto>(blog);
        }
    }

映射Domain对象

上面完成后我们就可以启动系统看到我们定义的接口了,但是我们还少了一步那就是映射 Domain 对象(实体和值类型)到数据库表。

演示

CoreDbContext上下文中加入我们的实体,然后在 CoreEfCoreEntityExtensionMappings 中新建一个静态ConfigureBcvpBlogCore方法写FluentApi,这里有几个疑惑我说下,因为我目前使用的版本是4.4也就是ABP刚发布的新版本,这个版本中它移除了一些类比如ModelBuilderConfigurationOptionsDbContextModelBuilderExtensions,我就直接把ConfigureBcvpBlogCore写在CoreEfCoreEntityExtensionMappings里面了,可能后面我会在找合理的地方去单独放,另外可以看到PostTag没有出现在这里,这是因为PostTag是一个值对象作为实体的私有类型处理了,这里就能充分感受到模型建立与数据库映射抽离。


----------------------------- CoreDbContext.cs

        public DbSet<BlogCore.Blogs.Blog> Blogs { get; set; }

        public DbSet<Post> Posts { get; set; }

        public DbSet<Tag> Tags { get; set; }

        public DbSet<Comment> Comments { get; set; }


        protected override void OnModelCreating(ModelBuilder builder)
        {

            // 这里是追加不是删掉原来的
            builder.ConfigureBcvpBlogCore();

        }



----------------------------- CoreEfCoreEntityExtensionMappings.cs

 public static void ConfigureBcvpBlogCore([NotNull] this ModelBuilder builder)
        {
            Check.NotNull(builder, nameof(builder));

            if (builder.IsTenantOnlyDatabase())
            {
                return;
            }


            builder.Entity<BlogCore.Blogs.Blog>(b =>
            {
                b.ToTable(CoreConsts.DbTablePrefix + "Blogs", CoreConsts.DbSchema);

                b.ConfigureByConvention();

                b.Property(x => x.Name).IsRequired().HasMaxLength(BlogConsts.MaxNameLength).HasColumnName(nameof(BlogCore.Blogs.Blog.Name));
                b.Property(x => x.ShortName).IsRequired().HasMaxLength(BlogConsts.MaxShortNameLength).HasColumnName(nameof(BlogCore.Blogs.Blog.ShortName));
                b.Property(x => x.Description).IsRequired(false).HasMaxLength(BlogConsts.MaxDescriptionLength).HasColumnName(nameof(BlogCore.Blogs.Blog.Description));

                b.ApplyObjectExtensionMappings();
            });

            builder.Entity<Post>(b =>
            {
                b.ToTable(CoreConsts.DbTablePrefix + "Posts", CoreConsts.DbSchema);

                b.ConfigureByConvention();

                b.Property(x => x.BlogId).HasColumnName(nameof(Post.BlogId));
                b.Property(x => x.Title).IsRequired().HasMaxLength(PostConsts.MaxTitleLength).HasColumnName(nameof(Post.Title));
                b.Property(x => x.CoverImage).IsRequired().HasColumnName(nameof(Post.CoverImage));
                b.Property(x => x.Url).IsRequired().HasMaxLength(PostConsts.MaxUrlLength).HasColumnName(nameof(Post.Url));
                b.Property(x => x.Content).IsRequired(false).HasMaxLength(PostConsts.MaxContentLength).HasColumnName(nameof(Post.Content));
                b.Property(x => x.Description).IsRequired(false).HasMaxLength(PostConsts.MaxDescriptionLength).HasColumnName(nameof(Post.Description));

                b.OwnsMany(p => p.Tags, pd =>
                {
                    pd.ToTable(CoreConsts.DbTablePrefix + "PostTags", CoreConsts.DbSchema);

                    pd.Property(x => x.TagId).HasColumnName(nameof(PostTag.TagId));
                    
                });

                b.HasOne<BlogCore.Blogs.Blog>().WithMany().IsRequired().HasForeignKey(p => p.BlogId);

                b.ApplyObjectExtensionMappings();
            });

            builder.Entity<Tag>(b =>
            {
                b.ToTable(CoreConsts.DbTablePrefix + "Tags", CoreConsts.DbSchema);

                b.ConfigureByConvention();

                b.Property(x => x.Name).IsRequired().HasMaxLength(TagConsts.MaxNameLength).HasColumnName(nameof(Tag.Name));
                b.Property(x => x.Description).HasMaxLength(TagConsts.MaxDescriptionLength).HasColumnName(nameof(Tag.Description));
                b.Property(x => x.UsageCount).HasColumnName(nameof(Tag.UsageCount));

                b.ApplyObjectExtensionMappings();
            });


            builder.Entity<Comment>(b =>
            {
                b.ToTable(CoreConsts.DbTablePrefix + "Comments", CoreConsts.DbSchema);

                b.ConfigureByConvention();

                b.Property(x => x.Text).IsRequired().HasMaxLength(CommentConsts.MaxTextLength).HasColumnName(nameof(Comment.Text));
                b.Property(x => x.RepliedCommentId).HasColumnName(nameof(Comment.RepliedCommentId));
                b.Property(x => x.PostId).IsRequired().HasColumnName(nameof(Comment.PostId));

                b.HasOne<Comment>().WithMany().HasForeignKey(p => p.RepliedCommentId);
                b.HasOne<Post>().WithMany().IsRequired().HasForeignKey(p => p.PostId);

                b.ApplyObjectExtensionMappings();
            });


         

            builder.TryConfigureObjectExtensions<CoreDbContext>();

        }

接下来就是生成迁移和执行迁移了

创建项目

结语

本节知识点:

  • 1.根据前面4章讲的知识完成博客建模
  • 2.完成业务博客业务代码
  • 3.自定义仓储

联系作者:加群:867095512 @MrChuJiu

公众号

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/154884.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 记一次线上服务器宕机 springboot tomcat

    记一次线上服务器宕机 springboot tomcat记一次线上服务器宕机springboottomcat今天点网站发现请求不了了,到服务器查看,发现tomcat死了。查看log发现但是项目本地跑,没发现问题。查看了一下项目,怀疑是定时任务占用线程池满导致内存泄漏具体看一下定时任务中有没有暂时重启服务器让服务跑通…

  • bat批量删除空文件夹_如何建立bat文件夹

    bat批量删除空文件夹_如何建立bat文件夹@echooff&amp;title清理空目录set/pPan=请输入要清理文件夹,回车确认:cls&amp;echo即将开始清理。。。&amp;ping0-n"3"&gt;nulcd/d%Pan%for/f"delims="%%ain(‘dir/ad/s/b’)do(dir/a-d/s/b"%%~a\*"&gt;nul2&gt;nul..

  • 动态载入TreeView时让TreeView节点前显示加号

    动态载入TreeView时让TreeView节点前显示加号

  • java激活码(JetBrains全家桶)

    (java激活码)本文适用于JetBrains家族所有ide,包括IntelliJidea,phpstorm,webstorm,pycharm,datagrip等。IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.cn/100143.html…

  • 二小姐对群环域的理解

    二小姐对群环域的理解从本质上来看,群=非空集合+二元运算,群的定义主要包括四个方面:封闭性:二元运算的定义就可以满足这个性质 结合律:可以确保多个元素运算时得到唯一的结果,不受运算先后的影响,从而有(或na)的表达式 单位元:唯一 逆元:任意元素均有且唯一特殊的群为循环群;群举例:Z(加法);Zn(加法)明确了群的定义后,我们接着了解群的各类特殊子群的定义和性质:子群H=群G的子集合+二元运算…

  • python中sqrt函数用法_sqrt是什么函数[通俗易懂]

    python中sqrt函数用法_sqrt是什么函数[通俗易懂]sqrt是什么函数?sqrt()是用于计算数字x的平方根的函数。语法以下是sqrt()方法的语法:importmathmath.sqrt(x)注意:sqrt()是不能直接访问的,需要导入math模块,通过静态对象调用该方法。参数x–数值表达式。返回值返回数字x的平方根。实例以下展示了使用sqrt()方法的实例:#!/usr/bin/pythonimportmath#…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号