iT邦幫忙

2024 iThome 鐵人賽

DAY 12
0
Software Development

DDD? Clean Architecture? Microservices? 帶你用.NET實作打造一個現代化微服務!系列 第 12

Day 12 - 深入 DDD 核心:Account 和 Todo 服務的 Aggregate 實作

  • 分享至 

  • xImage
  •  

前言

首先,我們先把我們 DDD Model Process 後的 Aggregate Model 實作在 Domain Layer。

我們回顧一下這些 Aggregates 都有甚麼功能:

Strategic Design 11

Reference Project

我們前一章節把 DDD 最基本的 Seedwork 實作在 Common Library 中,所以一開始,我們先 Reference Common Library 到我們的專案內。

  <ItemGroup>
    <ProjectReference Include="..\..\Common\Common.Library\Common.Library.csproj" />
  </ItemGroup>

Account Service Domain

Account.Domain 來實作。

User Aggregate

複習一下 User Aggregate 長甚麼樣子

{
  "id": { "value": "00000000-0000-0000-0000-000000000000" },
  "firstName": "Tiffany",
  "lastName": "Doe",
  "email": "user@gmail.com",
  "password": "p@ssw0rd!", // Need hash
  "createdDateTime": "2024-01-01T00:00:00.0000000Z",
  "updatedDateTime": "2024-01-01T00:00:00.0000000Z"
}

在先前,我們把 Aggregate Root ID 都定義成 ValueObject,

"id": { "value": "00000000-0000-0000-0000-000000000000" }

所以我們在 Domain Layer 先創立一個 ValueObjects 的資料夾,並在裡面新增 UserId.cs

using Common.Library.Seedwork;

namespace Account.Domain.ValueObjects;

public class UserId : ValueObject
{
    public Guid Value { get; private set; }

    public UserId(Guid value)
    {
        Value = value;
    }

    public static UserId Create()
    {
        return new(Guid.NewGuid());
    }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Value;
    }
}

接著就可以來實作 User Aggregate,我們先創建 Aggregates 資料夾,並新增 user.cs

User Aggregate 主要會有這兩種功能:

  1. Register 的體現就是 Create 一個新的 User。
  2. Login 的體現就是我們需要對輸入的 Password 做 Verify。

另外,依照定義,每個 Aggregate 本身需要一個 Aggregate Root,並且該 Aggregate Root 本身就是 Entity,所以這個 User Class 需要繼承 EntityIAggregateRoot,其實作如下:

using Account.Domain.ValueObjects;
using Common.Library.Seedwork;

namespace Account.Domain.Aggregates;

public class User : Entity<UserId>, IAggregateRoot
{
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public string Email { get; private set; }
    public string Password { get; private set; }

    private User() { }

    public User(UserId id, string firstName, string lastName, string email, string password)
    {
        Id = id;
        FirstName = firstName;
        LastName = lastName;
        Email = email;
        Password = password;
        CreatedDateTime = DateTime.UtcNow;
        UpdatedDateTime = DateTime.UtcNow;
    }

    public static User Create(
        string firstName,
        string lastName,
        string email,
        string password)
        => new()
        {
            Id = UserId.Create(),
            FirstName = firstName,
            LastName = lastName,
            Email = email,
            Password = password,
            CreatedDateTime = DateTime.UtcNow,
            UpdatedDateTime = DateTime.UtcNow
        };

    public bool VerifyPassword(string password) => Password == password;

}

Account Service File Architect

https://ithelp.ithome.com.tw/upload/images/20240926/20168953PqLKeEWvao.png

Todo Service Domain

Todo.Domain 來實作,裡面有兩個 Aggregates,Todo ListTodo Item

Todo List Aggregate

複習一下 Todo List Aggregate 長甚麼樣子

Todo List

{
  "id": { "value": "00000000-0000-0000-0000-000000000000" },
  "name": "Default",
  "description": "Default",
  "status": "active", // active, removed
  "userId": { "value": "00000000-0000-0000-0000-000000000000" },
  "itemIds": [
    { "value": "00000000-0000-0000-0000-000000000000" }
  ],
  "createdDateTime": "2024-01-01T00:00:00.0000000Z",
  "updatedDateTime": "2024-01-01T00:00:00.0000000Z"
}

接著就開始依樣畫葫蘆:

TodoListId

using Common.Library.Seedwork;

namespace Todo.Domain.ValueObjects;

public class TodoListId : ValueObject
{
    public Guid Value { get; private set; }
    
    public TodoListId(Guid value)
    {
        Value = value;
    }

    public static TodoListId Create()
    {
        return new(Guid.NewGuid());
    }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Value;
    }
}

TodoListStatus

"status": "active", // active, removed

namespace Todo.Domain.ValueObjects.Enums;

public enum TodoListStatus
{
    Active,
    Removed
}

TodoList

using Common.Library.Seedwork;
using Todo.Domain.ValueObjects;
using Todo.Domain.ValueObjects.Enums;

namespace Todo.Domain.Aggregates;

public class TodoList : Entity<TodoListId>, IAggregateRoot
{
    public string Name { get; private set; } = string.Empty;
    public string Description { get; private set; } = string.Empty;
    public TodoListStatus Status { get; set; }
    public Guid UserId { get; private set; }
    public ICollection<Guid> TodoItemIds { get; set; } = new List<Guid>();

    private TodoList() { }

    public TodoList(TodoListId id, string name, string description, Guid userId)
    {
        Id = id;
        Name = name;
        Description = description;
        Status = TodoListStatus.Active;
        UserId = userId;
        CreatedDateTime = DateTime.UtcNow;
        UpdatedDateTime = DateTime.UtcNow;
    }

    public static TodoList Create(
    string name,
    string description,
    Guid userId)
    => new()
    {
        Id = TodoListId.Create(),
        Name = name,
        Description = description,
        Status = TodoListStatus.Active,
        UserId = userId,
        CreatedDateTime = DateTime.UtcNow,
        UpdatedDateTime = DateTime.UtcNow
    };

    public void Remove()
    {
        Status = TodoListStatus.Removed;
        UpdatedDateTime = DateTime.UtcNow;
    }
}

Todo Item Aggregate

複習一下 Todo Item Aggregate 長甚麼樣子

{
  "id": { "value": "00000000-0000-0000-0000-000000000000" },
  "content": "Do homework!",
  "todoItemStatus": {
    "status": "todo", // todo, finished, removed
    "color": "#FFFFFF"
  },
  "listId": { "value": "00000000-0000-0000-0000-000000000000" },
  "createdDateTime": "2024-01-01T00:00:00.0000000Z",
  "updatedDateTime": "2024-01-01T00:00:00.0000000Z"
}

這邊的 todoItemStatus 比較特別,需求是每個狀態都會有一個對應的顏色:

  1. todo => 黃色
  2. finished => 綠色
  3. removed => 灰色

所以 todoItemStatus 這個 Value Object 我們需要特別制定一下:

TodoItemStatus

using Common.Library.Seedwork;
using Todo.Domain.ValueObjects.Enums;

namespace Todo.Domain.ValueObjects;

public class TodoItemStatus : ValueObject
{
    public TodoItemState State { get; private set; }
    public string Color => GetColorByState(State); // 顏色依據狀態自動計算

    public TodoItemStatus(TodoItemState state)
    {
        State = state;
    }

    public static TodoItemStatus Default() => new (TodoItemState.Todo);

    private string GetColorByState(TodoItemState state)
    {
        return state switch
        {
            TodoItemState.Todo => "#FFFF00", // 黃色
            TodoItemState.Finished => "#008000", // 綠色
            TodoItemState.Removed => "#808080", // 灰色
            _ => throw new ArgumentOutOfRangeException(nameof(state), $"Unknown state: {state}")
        };
    }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return State;
    }
}

TodoItemState

namespace Todo.Domain.ValueObjects.Enums
{
    public enum TodoItemState
    {
        Todo,
        Finished,
        Removed
    }
}

TodoItemId

using Common.Library.Seedwork;

namespace Todo.Domain.ValueObjects;

public class TodoItemId : ValueObject
{
    public Guid Value { get; private set; }
    
    public TodoItemId(Guid value)
    {
        Value = value;
    }

    public static TodoItemId Create()
    {
        return new(Guid.NewGuid());
    }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Value;
    }
}

TodoItem

using Common.Library.Seedwork;
using Todo.Domain.ValueObjects;
using Todo.Domain.ValueObjects.Enums;

namespace Todo.Domain.Aggregates;

public class TodoItem : Entity<TodoItemId>, IAggregateRoot
{
    public string Content { get; private set; } = string.Empty;
    public TodoItemStatus Status { get; set; } = TodoItemStatus.Default();
    public Guid ListId { get; private set; }

    private TodoItem() { }

    public TodoItem(TodoItemId id, string content, Guid listId)
    {
        Id = id;
        Content = content;
        Status = TodoItemStatus.Default();
        ListId = listId;
        CreatedDateTime = DateTime.UtcNow;
        UpdatedDateTime = DateTime.UtcNow;
    }

    public static TodoItem Create(
        string content,
        Guid listId)
        => new()
        {
            Id = TodoItemId.Create(),
            Content = content,
            Status = TodoItemStatus.Default(),
            ListId = listId,
            CreatedDateTime = DateTime.UtcNow,
            UpdatedDateTime = DateTime.UtcNow
        };

    public void MarkAsFinished()
    {
        Status = new TodoItemStatus(TodoItemState.Finished);
        UpdatedDateTime = DateTime.UtcNow;
    }

    public void Remove()
    {
        Status = new TodoItemStatus(TodoItemState.Removed);
        UpdatedDateTime = DateTime.UtcNow;
    }

}

Todo Service File Architect

打完收工,結果如下:

https://ithelp.ithome.com.tw/upload/images/20240926/20168953mniTeXoXRM.png

後語

計算了一下未來的篇章,發現不能寫太少,不然30篇真的寫不完。明天開始針對功能來開發。


上一篇
Day 11 - 從零開始:構建你的 DDD Seedwork
下一篇
Day 13 - Account Service 實作:Register 帳號註冊
系列文
DDD? Clean Architecture? Microservices? 帶你用.NET實作打造一個現代化微服務!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言