Abp vNext 基礎篇丨領域構建

初久的私房菜發表於2021-08-23

介紹

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

領域劃分

首先我們先對比下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

公眾號

相關文章