五、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

公众号

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

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 回溯算法 js_回溯算法代码

    回溯算法 js_回溯算法代码回溯算法是算法设计中的一种回溯算法是一种渐进式寻找并构建问题解决方式的策略回溯算法会先从一个可能的动作开始解决问题,如果不行,就回溯并选择另一个动作,直到将问题解决使用场景有很多路在这些路中,有死路和出路通常需要递归来模拟所有的路leetcode46:全排列解题思路要求:1所有排列情况;2没有重复元素有出路有死路使用回溯算法解题步骤用递归模拟出所有情况遇到包含重复元素的情况,就回溯收集所有到达递归终点的情况,并返回code//时间复杂度O.

    2022年10月4日
    3
  • 小技巧两个感叹号(两个!)连用

    小技巧两个感叹号(两个!)连用

    2021年11月8日
    287
  • 【JAVA】【NIO】3、Java NIO Channel

    【JAVA】【NIO】3、Java NIO Channel

    2022年1月3日
    53
  • Java面向对象的三大特征以及理解

    Java面向对象的三大特征以及理解Java面向对象的三大特征为:封装、继承和多态,本文说说我对三大特性的理解。1.封装Java中的封装是指一个类把自己内部的实现细节进行隐藏,只暴露对外的接口(setter和getter方法)。封装又分为属性的封装和方法的封装。把属性定义为私有的,它们通过setter和getter方法来对属性的值进行设定和获取。下面我举一个简单的封装例子publicclassPerson{priva…

    2022年7月25日
    9
  • 微信小程序-代码结构介绍

    微信小程序-代码结构介绍微信小程序-代码结构介绍

    2022年4月25日
    40
  • char (*p)[]和char *p[]的区别[建议收藏]

    理解的关键在于:1.[]的优先级高于*,(*p)[]理解为指向一个数组,*(p[])存放指针的数组2. char(*p)[SIZE]:指向一维数组的指针,一维数组只能有SIZE个元素

    2021年12月18日
    38

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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