iT邦幫忙

2024 iThome 鐵人賽

DAY 19
0

前篇作業

先來完成上一章節最後提到的 Remove List 作業。

todoList.proto

syntax = "proto3";

option csharp_namespace = "Todo.Grpc";

service TodoListGrpcService {
  rpc CreateTodoList (CreateTodoListRequest) returns (TodoListResponse);
  rpc RemoveTodoList (RemoveTodoListRequest) returns (TodoListResponse);
}

message CreateTodoListRequest {
  string UserId = 1;
  string Name = 2;
  string Description = 3;
}

message RemoveTodoListRequest {
  string Id = 1;
}

message TodoListResponse {
  string Id = 1;
  string UserId = 2;
  string Name = 3;
  string Description = 4;
  string Status = 5;
}

GrpcTodoListService 實作 RemoveTodoList

using Grpc.Core;
using Todo.Application;

namespace Todo.Grpc.Services;

public class GrpcTodoListService : TodoListGrpcService.TodoListGrpcServiceBase
{
    private readonly ITodoListService _todoListService;

    public GrpcTodoListService(ITodoListService todoListService)
    {
        this._todoListService = todoListService;
    }

    public override Task<TodoListResponse> CreateTodoList(CreateTodoListRequest request, ServerCallContext context)
    {
        var result = _todoListService.CreateTodoList(new Guid(request.UserId), request.Name, request.Description);

        TodoListResponse response = new()
        {
            Id = result.Id.ToString(),
            UserId = result.UserId.ToString(),
            Name = result.Name,
            Description = result.Description,
            Status = result.Status.ToString()
        };

        return Task.FromResult(response);
    }

    public override Task<TodoListResponse> RemoveTodoList(RemoveTodoListRequest request, ServerCallContext context)
    {
        var result = _todoListService.RemoveTodoList(new Guid(request.Id));

        TodoListResponse response = new()
        {
            Id = result.Id.ToString(),
            UserId = result.UserId.ToString(),
            Name = result.Name,
            Description = result.Description,
            Status = result.Status.ToString()
        };

        return Task.FromResult(response);
    }
}

ITodoListService

namespace Todo.Application;

public interface ITodoListService
{
    TodoListResult CreateTodoList(Guid userId, string name, string description);
    TodoListResult RemoveTodoList(Guid guid);
}

TodoListService

using System.ComponentModel;
using Todo.Domain.Aggregates;

namespace Todo.Application;

public class TodoListService : ITodoListService
{
    private static readonly List<TodoList> _todoLists = new();
    
    public TodoListResult CreateTodoList(Guid userId, string name, string description)
    {
        var list = TodoList.Create(name, description, userId);

        _todoLists.Add(list);

        return new TodoListResult(list.Id.Value, list.UserId, list.Name, list.Description, list.Status);
    }

    public TodoListResult RemoveTodoList(Guid guid)
    {
        var list = _todoLists.SingleOrDefault(i => i.Id.Value == guid);

        if (list == null)
            throw new ArgumentException("Todo list not exists");

        list.Remove();

        return new TodoListResult(list.Id.Value, list.UserId, list.Name, list.Description, list.Status);
    }
}

回歸主題

我們在 Todo Service 的實作上,在 Application Layer 的 Services 都直接使用一個 Static List 來存在記憶體,現在需要把功能分離出 Application Layer 並交由 Infrastructure 來實作,這時候就要建立一個 Repository 來使業思務邏輯與資料庫的 CRUD 解耦。Repository Pattern 的核心想是讓程式碼只與 Repository 介面(IRepository)進行互動,而不直接操作資料庫或 ORM(如 Entity Framework)。透過具體實作這個介面的類別來與資料庫進行實際的操作。這樣,當你需要切換資料庫或更改資料存取邏輯時,只需要修改 Repository 的實作,而不會影響到其他程式碼。

https://ithelp.ithome.com.tw/upload/images/20241002/20168953O3oijAwSWy.png

建立 TodoListRepository

Repository 介面

先用 ITodoListRepositoryITodoItemRepository 介面取代 Application Service 上所有相關的操作

using Todo.Domain.Aggregates;

namespace Todo.Application;

public class TodoListService : ITodoListService
{
    private readonly ITodoListRepository _todoListRepository;

    public TodoListService(ITodoListRepository todoListRepository)
    {
        this._todoListRepository = todoListRepository;
    }
    
    public TodoListResult CreateTodoList(Guid userId, string name, string description)
    {
        var list = TodoList.Create(name, description, userId);

        _todoListRepository.Add(list);

        return new TodoListResult(list.Id.Value, list.UserId, list.Name, list.Description, list.Status);
    }

    public TodoListResult RemoveTodoList(Guid guid)
    {
        var list = _todoListRepository.GetByGuid(guid);

        if (list == null)
            throw new ArgumentException("Todo list not exists");

        list.Remove();

        return new TodoListResult(list.Id.Value, list.UserId, list.Name, list.Description, list.Status);
    }
}
using Todo.Domain.Aggregates;
using Todo.Domain.ValueObjects;

namespace Todo.Application;

public class TodoItemService : ITodoItemService
{
    private readonly ITodoItemRepository _todoItemRepository;

    public TodoItemService(ITodoItemRepository todoItemRepository)
{
        this._todoItemRepository = todoItemRepository;
    }

    public TodoItemResult CreateTodoItem(Guid userId, string content)
    {
        var item = TodoItem.Create(content, userId);

        _todoItemRepository.Add(item);

        return new TodoItemResult(item.Id.Value, item.ListId, item.Content, item.Status);
    }

    public TodoItemResult FinishTodoItem(Guid guid)
    {
        var item = _todoItemRepository.GetByGuid(guid);

        if (item == null)
            throw new ArgumentException("Todo item not exists");
        if (item.Status != new TodoItemStatus(Domain.ValueObjects.Enums.TodoItemState.Todo))
            throw new ArgumentException("Todo item cannot be finished");

        item.MarkAsFinished();
        
        return new TodoItemResult(item.Id.Value, item.ListId, item.Content, item.Status);
    }

    public TodoItemResult RemoveTodoItem(Guid guid)
    {
        var item = _todoItemRepository.GetByGuid(guid);

        if (item == null)
            throw new ArgumentException("Todo item not exists");

        item.Remove();
        
        return new TodoItemResult(item.Id.Value, item.ListId, item.Content, item.Status);
    }
}

接著產生 ITodoListRepositoryITodoItemRepository 介面

using Todo.Domain.Aggregates;

namespace Todo.Application;

public interface ITodoListRepository : IRepository<TodoList>
{
    void Add(TodoList list);
    TodoList? GetByGuid(Guid guid);
}
using Todo.Domain.Aggregates;

namespace Todo.Application;

public interface ITodoItemRepository : IRepository<TodoItem>
{
    void Add(TodoItem item);
    TodoItem? GetByGuid(Guid guid);
}

Repository 類別實作

然後在 Infrastructure Layer 建立 TodoListRepositoryTodoItemRepository 實作

using Common.Library.Seedwork;
using Todo.Application;
using Todo.Domain.Aggregates;

namespace Todo.Infrastructure;

public class TodoListRepository : ITodoListRepository
{
    private static readonly List<TodoList> todoLists = new List<TodoList>();

    public IUnitOfWork UnitOfWork => throw new NotImplementedException();

    public void Add(TodoList list)
    {
        todoLists.Add(list);
    }

    public TodoList? GetByGuid(Guid guid)
    {
        return todoLists.SingleOrDefault(x => x.Id.Value == guid);
    }
}
using Common.Library.Seedwork;
using Todo.Application;
using Todo.Domain.Aggregates;

namespace Todo.Infrastructure;

public class TodoItemRepository : ITodoItemRepository
{
    private static readonly List<TodoItem> TodoItems = new List<TodoItem>();

    public IUnitOfWork UnitOfWork => throw new NotImplementedException();

    public void Add(TodoItem item)
    {
        TodoItems.Add(item);
    }

    public TodoItem? GetByGuid(Guid guid)
    {
        return TodoItems.SingleOrDefault(x => x.Id.Value == guid);
    }
}

DI Register

接著 DI 剛做好的 Repositories。新增 TodoInfrastructureRegister.cs 如下:

using Microsoft.Extensions.DependencyInjection;
using Todo.Application;

namespace Todo.Infrastructure;

public static class TodoInfrastructureRegister
{
    public static IServiceCollection AddTodoInfrastructure(this IServiceCollection services)
    {
        services.AddScoped<ITodoListRepository, TodoListRepository>();
        services.AddScoped<ITodoItemRepository, TodoItemRepository>();

        return services;
    }
}

然後在 Program.cs 使用 AddTodoInfrastructure()

builder.Services.AddTodoApplication().AddTodoInfrastructure();

gRPC Clicker Test

簡單測試一下所有功能都還正常

Create Todo List

https://ithelp.ithome.com.tw/upload/images/20241002/20168953OIFPSEOX9k.png

Remove Todo List

https://ithelp.ithome.com.tw/upload/images/20241002/20168953U2GS58T85s.png

Create Todo Item

https://ithelp.ithome.com.tw/upload/images/20241002/20168953VKrzUQMAlL.png

Finish Todo Item

https://ithelp.ithome.com.tw/upload/images/20241002/20168953vFCGHmBwGd.png

Remove Todo Item

https://ithelp.ithome.com.tw/upload/images/20241002/20168953ETjBl3TymK.png

結語

下一章節實作 Todo Service 的 ORM,讓資料真的存進 Database 內。

https://ithelp.ithome.com.tw/upload/images/20241002/20168953uQ5tXZPHii.png


上一篇
Day 18 - Todo Service 實作:利用 Quick Fix 快速完成 Finish/Remove Functions
下一篇
Day 20 - Todo Infrastructure 實作:複習 Entity Framework
系列文
DDD? Clean Architecture? Microservices? 帶你用.NET實作打造一個現代化微服務!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言