首先,我們先把我們 DDD Model Process 後的 Aggregate Model 實作在 Domain Layer。
我們回顧一下這些 Aggregates 都有甚麼功能:
我們前一章節把 DDD 最基本的 Seedwork 實作在 Common Library 中,所以一開始,我們先 Reference Common Library 到我們的專案內。
<ItemGroup>
<ProjectReference Include="..\..\Common\Common.Library\Common.Library.csproj" />
</ItemGroup>
到 Account.Domain
來實作。
複習一下 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 主要會有這兩種功能:
另外,依照定義,每個 Aggregate 本身需要一個 Aggregate Root,並且該 Aggregate Root 本身就是 Entity,所以這個 User
Class 需要繼承 Entity
和 IAggregateRoot
,其實作如下:
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;
}
到 Todo.Domain
來實作,裡面有兩個 Aggregates,Todo List
和 Todo Item
。
複習一下 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"
}
接著就開始依樣畫葫蘆:
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;
}
}
"status": "active", // active, removed
namespace Todo.Domain.ValueObjects.Enums;
public enum TodoListStatus
{
Active,
Removed
}
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 長甚麼樣子
{
"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
比較特別,需求是每個狀態都會有一個對應的顏色:
所以 todoItemStatus
這個 Value Object 我們需要特別制定一下:
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;
}
}
namespace Todo.Domain.ValueObjects.Enums
{
public enum TodoItemState
{
Todo,
Finished,
Removed
}
}
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;
}
}
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;
}
}
打完收工,結果如下:
計算了一下未來的篇章,發現不能寫太少,不然30篇真的寫不完。明天開始針對功能來開發。