四、Abp vNext 基礎篇丨領域構建


介紹

我們將通過例⼦介紹和解釋⼀些顯式規則。在實現領域驅動設計時,應該遵循這些規則並將其應⽤到解決⽅案中。

領域划分

首先我們先對比下Blog.Core和本次重構設計上的偏差,可以看到多了一個博客管理和類別管理。

業務腦圖

根據上面得到的業務腦圖我們可以看到包含Blog(博客),Post(文章),Comment(評論),Tag(標簽),User(用戶),根據腦圖畫出領域圖來指明關系。

領域圖

領域圖連接地址:https://www.processon.com/view/link/611365c00e3e7407d39727ee

聚合根最佳實踐

只通過ID引⽤其他聚合

⼀個聚合應該只通過其他聚合的ID引⽤聚合,這意味着你不能添加導航屬性到其他聚合。

  • 這條規則使得實現可序列化原則得以實現。

  • 可以防⽌不同聚合相互操作,以及將聚合的業務邏輯泄露給另⼀個聚合。

來看下面的2個聚合根 Blog 和 Post.

  • Blog 沒有包含 Post集合,因為他們是不同聚合
  • Post 使用 BlogId 關聯 Blog

當你有一個 Post 需要關聯 Blog的時候 你可以從數據庫通過 BlogId 進行獲取

    public class Blog:FullAuditedAggregateRoot<Guid>
    {
        [NotNull]
        public virtual string Name { get; set; }

        [NotNull]
        public virtual string ShortName { get; set; }

        [CanBeNull]
        public virtual string Description { get; set; }
    }

    public class Post : FullAuditedAggregateRoot<Guid>
    {
        public virtual Guid BlogId { get; protected set; }

        [NotNull]
        public virtual string Url { get; protected set; }

        [NotNull]
        public virtual string CoverImage { get; set; }

        [NotNull]
        public virtual string Title { get; protected set; }

        [CanBeNull]
        public virtual string Content { get; set; }

        [CanBeNull]
        public virtual string Description { get; set; }

        public virtual int ReadCount { get; protected set; }

        public virtual Collection<PostTag> Tags { get; protected set; }
    }

聚合根/實體中的主鍵

⼀個聚合根通常有⼀個ID屬性作為其標識符(主鍵,Primark Key: PK)。推薦使⽤ Guid 作為聚合,聚合中的實體(不是聚合根)可以使⽤復合主鍵(后面講),主鍵ABP已經幫我們做好了參閱文檔:https://docs.abp.io/en/abp/latest/Entities。

    public class Blog:FullAuditedAggregateRoot<Guid>
    {
        [NotNull]
        public virtual string Name { get; set; }

        [NotNull]
        public virtual string ShortName { get; set; }

        [CanBeNull]
        public virtual string Description { get; set; }
    }

聚合根/實體構造函數

構造函數是實體的⽣命周期開始的地⽅。⼀個設計良好的構造函數,擔負以下職責:

  • 獲取所需的實體屬性參數,來創建⼀個有效的實體。應該強制只傳遞必要的參數,並可以將⾮必要 的屬性作為可選參數。
  • 檢查參數的有效性。
  • 初始化⼦集合。

        public Blog(Guid id, [NotNull] string name, [NotNull] string shortName)
        {
            //屬性賦值
            Id = id;
            //有效性檢測
            Name = Check.NotNullOrWhiteSpace(name, nameof(name));
            //有效性檢測
            ShortName = Check.NotNullOrWhiteSpace(shortName, nameof(shortName));
        }

  • Blog 類通過構造函數參數、獲得屬性所需的值,以此創建一個正確有效的實體
  • 在構造函數中驗證輸⼊參數的有效性,⽐如: Check.NotNullOrWhiteSpace(...) 當傳遞的值為空 時,拋出異常 ArgumentException
  • 構造函數將參數 id 傳遞給 base 類,不在構造函數中⽣成 Guid,可以將其委托給另⼀個 Guid⽣成 服務,作為參數傳遞進來
  • ⽆參構造函數對於ORM是必要的。我們將其設置為私有,以防⽌在代碼中意外地使⽤它

實體屬性訪問器和⽅法

上⾯的示例代碼,看起來可能很奇怪。⽐如:在構造函數中,我們強制傳遞⼀個不為 null 的 Name 。 但是,我們可以將 Name 屬性設置為 null ,⽽對其沒有進⾏任何有效性控制。

如果我們⽤ public 設置器聲明所有的屬性,就像上⾯的 Blog 類中的屬性例⼦,我們就不能在實體的⽣命周期中強制保持其有效性和完整性。所以:

  • 當需要在設置屬性時,執⾏任何邏輯,請將屬性設置為私有 private 。
  • 定義公共⽅法來操作這些屬性。
     public class Blog:FullAuditedAggregateRoot<Guid>
    {
        [NotNull]
        public virtual string Name { get; protected set; }

        [NotNull]
        public virtual string ShortName { get; protected set; }

        [CanBeNull]
        public virtual string Description { get; set; }

        protected Blog()
        {
            /*反序列化或ORM 需要*/
        }

        public Blog(Guid id, [NotNull] string name, [NotNull] string shortName)
        {
            //屬性賦值
            Id = id;
            //有效性檢測
            Name = Check.NotNullOrWhiteSpace(name, nameof(name));
            //有效性檢測
            ShortName = Check.NotNullOrWhiteSpace(shortName, nameof(shortName));
        }

        public virtual Blog SetName([NotNull] string name)
        {
            Name = Check.NotNullOrWhiteSpace(name, nameof(name));
            return this;
        }

        public virtual Blog SetShortName(string shortName)
        {
            ShortName = Check.NotNullOrWhiteSpace(shortName, nameof(shortName));
            return this;
        }

    }

業務邏輯和實體中的異常處理

當你在實體中進⾏驗證和實現業務邏輯,經常需要管理異常:

  • 創建特定領域異常。
  • 必要時在實體⽅法中拋出這些異常

ABP框架 Exception Handing 系統處理了這些問題。

完成聚合的實體創建

根據 最佳實踐的講解完成,把其他實體創建出來,代碼粘在這里了。

實體

public class Blog:FullAuditedAggregateRoot<Guid>
    {
        [NotNull]
        public virtual string Name { get; protected set; }

        [NotNull]
        public virtual string ShortName { get; protected set; }

        [CanBeNull]
        public virtual string Description { get; set; }

        protected Blog()
        {

        }

        public Blog(Guid id, [NotNull] string name, [NotNull] string shortName)
        {
            //屬性賦值
            Id = id;
            //有效性檢測
            Name = Check.NotNullOrWhiteSpace(name, nameof(name));
            //有效性檢測
            ShortName = Check.NotNullOrWhiteSpace(shortName, nameof(shortName));
        }

        public virtual Blog SetName([NotNull] string name)
        {
            Name = Check.NotNullOrWhiteSpace(name, nameof(name));
            return this;
        }

        public virtual Blog SetShortName(string shortName)
        {
            ShortName = Check.NotNullOrWhiteSpace(shortName, nameof(shortName));
            return this;
        }

    }





    public class Comment : FullAuditedAggregateRoot<Guid>
    {
        public virtual Guid PostId { get; protected set; }

        public virtual Guid? RepliedCommentId { get; protected set; }

        public virtual string Text { get; protected set; }

        protected Comment()
        {

        }

        public Comment(Guid id, Guid postId, Guid? repliedCommentId, [NotNull] string text)
        {
            Id = id;
            PostId = postId;
            RepliedCommentId = repliedCommentId;
            Text = Check.NotNullOrWhiteSpace(text, nameof(text));
        }

        public void SetText(string text)
        {
            Text = Check.NotNullOrWhiteSpace(text, nameof(text));
        }
    }






    public class Post : FullAuditedAggregateRoot<Guid>
    {
        public virtual Guid BlogId { get; protected set; }

        [NotNull]
        public virtual string Url { get; protected set; }

        [NotNull]
        public virtual string CoverImage { get; set; }

        [NotNull]
        public virtual string Title { get; protected set; }

        [CanBeNull]
        public virtual string Content { get; set; }

        [CanBeNull]
        public virtual string Description { get; set; }

        public virtual int ReadCount { get; protected set; }

        public virtual Collection<PostTag> Tags { get; protected set; }


        protected Post()
        {

        }

        public Post(Guid id, Guid blogId, [NotNull] string title, [NotNull] string coverImage, [NotNull] string url)
        {
            Id = id;
            BlogId = blogId;
            Title = Check.NotNullOrWhiteSpace(title, nameof(title));
            Url = Check.NotNullOrWhiteSpace(url, nameof(url));
            CoverImage = Check.NotNullOrWhiteSpace(coverImage, nameof(coverImage));

            Tags = new Collection<PostTag>();
            Comments = new Collection<Comment>();
        }

        public virtual Post IncreaseReadCount()
        {
            ReadCount++;
            return this;
        }

        public virtual Post SetTitle([NotNull] string title)
        {
            Title = Check.NotNullOrWhiteSpace(title, nameof(title));
            return this;
        }

        public virtual Post SetUrl([NotNull] string url)
        {
            Url = Check.NotNullOrWhiteSpace(url, nameof(url));
            return this;
        }

        public virtual void AddTag(Guid tagId)
        {
            Tags.Add(new PostTag(Id, tagId));
        }

        public virtual void RemoveTag(Guid tagId)
        {
            Tags.RemoveAll(t => t.TagId == tagId);
        }

    }






    public record PostTag 
    {
         public virtual Guid TagId { get; init; }  //主鍵

        protected PostTag()
        {

        }

        public PostTag( Guid tagId)
        {
            TagId = tagId;
        }
    }



     public class Tag : FullAuditedAggregateRoot<Guid>
    {
        public virtual Guid BlogId { get; protected set; }

        public virtual string Name { get; protected set; }

        public virtual string Description { get; protected set; }

        public virtual int UsageCount { get; protected internal set; }


        protected Tag()
        {

        }

        public Tag(Guid id, Guid blogId, [NotNull] string name, int usageCount = 0, string description = null)
        {
            Id = id;
            Name = Check.NotNullOrWhiteSpace(name, nameof(name));
            BlogId = blogId;
            Description = description;
            UsageCount = usageCount;
        }

        public virtual void SetName(string name)
        {
            Name = Check.NotNullOrWhiteSpace(name, nameof(name));
        }

        public virtual void IncreaseUsageCount(int number = 1)
        {
            UsageCount += number;
        }

        public virtual void DecreaseUsageCount(int number = 1)
        {
            if (UsageCount <= 0)
            {
                return;
            }

            if (UsageCount - number <= 0)
            {
                UsageCount = 0;
                return;
            }

            UsageCount -= number;
        }

        public virtual void SetDescription(string description)
        {
            Description = description;
        }

    }

結語

本節知識點:

  • 1.根據腦圖划分聚合
  • 2.根據領域圖在遵守DDD的聚合根規范的情況下創建聚合

聯系作者:加群:867095512 @MrChuJiu

公眾號


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM