DDD + CQRS + MediatR 專案架構

// ───────────────────────────────────────────── // Application/HumanResources/Queries/GetEmployeeList/ // ─────────────────────────────────────────────

public class EmployeeListQuery : IRequest<PagedResult> { public int Page { get; set; } = 1; public int PageSize { get; set; } = 10; public string? Keyword { get; set; } }

public class EmployeeListQueryHandler : IRequestHandler<EmployeeListQuery, PagedResult> { private readonly IEmployeeRepository _repository;

public EmployeeListQueryHandler(IEmployeeRepository repository)
{
    _repository = repository;
}

public async Task<PagedResult<EmployeeDto>> Handle(EmployeeListQuery request, CancellationToken cancellationToken)
{
    var (items, total) = await _repository.SearchAsync(request.Keyword, request.Page, request.PageSize);

    var result = new PagedResult<EmployeeDto>
    {
        TotalCount = total,
        Items = items.Select(e => new EmployeeDto
        {
            Id = e.Id,
            Name = e.Name,
            Department = e.Department,
            HireDate = e.HireDate
        }).ToList()
    };

    return result;
}

}

// ───────────────────────────────────────────── // Application/Common/Models/PagedResult.cs // ─────────────────────────────────────────────

public class PagedResult { public int TotalCount { get; set; } public List Items { get; set; } = new(); }

// ───────────────────────────────────────────── // Application/Common/Interfaces/IEmployeeRepository.cs // ─────────────────────────────────────────────

public interface IEmployeeRepository { Task<(List, int)> SearchAsync(string? keyword, int page, int pageSize); Task AddAsync(Employee employee); Task<Employee?> GetByIdAsync(Guid id); }

// ───────────────────────────────────────────── // Domain/Entities/Employee.cs // ─────────────────────────────────────────────

public class Employee { public Guid Id { get; private set; } public string Name { get; private set; } public string Department { get; private set; } public DateTime HireDate { get; private set; }

public Employee(Guid id, string name, string department, DateTime hireDate)
{
    Id = id;
    Name = name;
    Department = department;
    HireDate = hireDate;
}

}

// ───────────────────────────────────────────── // Application/HumanResources/Queries/GetEmployeeById/ // ─────────────────────────────────────────────

public record GetEmployeeByIdQuery(Guid Id) : IRequest;

public class GetEmployeeByIdHandler : IRequestHandler<GetEmployeeByIdQuery, EmployeeDto> { private readonly IEmployeeRepository _repository;

public GetEmployeeByIdHandler(IEmployeeRepository repository)
{
    _repository = repository;
}

public async Task<EmployeeDto> Handle(GetEmployeeByIdQuery request, CancellationToken cancellationToken)
{
    var employee = await _repository.GetByIdAsync(request.Id);
    if (employee == null)
        throw new NotFoundException("Employee not found");

    return new EmployeeDto
    {
        Id = employee.Id,
        Name = employee.Name,
        Department = employee.Department,
        HireDate = employee.HireDate
    };
}

}

// ───────────────────────────────────────────── // Application/HumanResources/Commands/CreateEmployee/ // ─────────────────────────────────────────────

public class CreateEmployeeDto { public string Name { get; set; } = string.Empty; public string Department { get; set; } = string.Empty; public DateTime HireDate { get; set; } }

public class CreateEmployeeValidator : AbstractValidator { public CreateEmployeeValidator() { RuleFor(x => x.Name).NotEmpty(); RuleFor(x => x.Department).NotEmpty(); RuleFor(x => x.HireDate).LessThanOrEqualTo(DateTime.Today); } }

public record CreateEmployeeCommand(CreateEmployeeDto Dto) : IRequest;

public class CreateEmployeeHandler : IRequestHandler<CreateEmployeeCommand, Guid> { private readonly IEmployeeRepository _repository;

public CreateEmployeeHandler(IEmployeeRepository repository)
{
    _repository = repository;
}

public async Task<Guid> Handle(CreateEmployeeCommand request, CancellationToken cancellationToken)
{
    var dto = request.Dto;
    var employee = new Employee(Guid.NewGuid(), dto.Name, dto.Department, dto.HireDate);

    await _repository.AddAsync(employee);
    return employee.Id;
}

}

// ───────────────────────────────────────────── // Application/HumanResources/EmployeeDto.cs // ─────────────────────────────────────────────

public class EmployeeDto { public Guid Id { get; set; } public string Name { get; set; } = string.Empty; public string Department { get; set; } = string.Empty; public DateTime HireDate { get; set; } }

// ───────────────────────────────────────────── // Infrastructure/Persistence/Repositories/EmployeeRepository.cs // ─────────────────────────────────────────────

public class EmployeeRepository : IEmployeeRepository { private readonly ApplicationDbContext _context;

public EmployeeRepository(ApplicationDbContext context)
{
    _context = context;
}

public async Task<(List<Employee>, int)> SearchAsync(string? keyword, int page, int pageSize)
{
    var query = _context.Employees.AsQueryable();

    if (!string.IsNullOrWhiteSpace(keyword))
        query = query.Where(e => e.Name.Contains(keyword) || e.Department.Contains(keyword));

    var total = await query.CountAsync();
    var items = await query.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();

    return (items, total);
}

public async Task AddAsync(Employee employee)
{
    _context.Employees.Add(employee);
    await _context.SaveChangesAsync();
}

public async Task<Employee?> GetByIdAsync(Guid id)
{
    return await _context.Employees.FindAsync(id);
}

}

// ───────────────────────────────────────────── // API/Controllers/HumanResourcesController.cs // ─────────────────────────────────────────────

[ApiController] [Route("api/[controller]")] [Produces("application/json")] [ApiExplorerSettings(GroupName = "Human Resources")] public class HumanResourcesController : ControllerBase { private readonly ISender _sender;

public HumanResourcesController(ISender sender)
{
    _sender = sender;
}

[HttpPost("employees")]
[ProducesResponseType(typeof(ApiResponse<Guid>), StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Create([FromBody] CreateEmployeeDto dto)
{
    var id = await _sender.Send(new CreateEmployeeCommand(dto));
    return CreatedAtAction(nameof(GetById), new { id }, ApiResponse.Success(id));
}

[HttpGet("employees/{id}")]
[ProducesResponseType(typeof(ApiResponse<EmployeeDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)]
public async Task<ActionResult<ApiResponse<EmployeeDto>>> GetById(Guid id)
{
    var employee = await _sender.Send(new GetEmployeeByIdQuery(id));
    return Ok(ApiResponse.Success(employee));
}

[HttpGet("employees")]
[ProducesResponseType(typeof(ApiResponse<PagedResult<EmployeeDto>>), StatusCodes.Status200OK)]
public async Task<ActionResult<ApiResponse<PagedResult<EmployeeDto>>>> GetList([FromQuery] EmployeeListQuery query)
{
    var result = await _sender.Send(query);
    return Ok(ApiResponse.Success(result));
}

}

Last updated