Skip to content

Latest commit

 

History

History
405 lines (303 loc) · 9.66 KB

README.md

File metadata and controls

405 lines (303 loc) · 9.66 KB

Summer

⭐基于.net 5的DDD项目结构

整洁架构(Clean Architecture) / 洋葱架构 示意图:

domain-driven-design-clean-architecture


Getting started

领域层(Domain Layer)

实体(Entity)

public class OrderItem : BaseEntity
{
     public int ProductId { get; private set; }
    
    // todo:
}

值对象(Value Object)

public class Address : ValueObject
{
    public String Street { get; private set; }
    public String City { get; private set; }
    
    // todo:
}

聚合(Aggregate) ,聚合根(Aggregate Root)

public class Order : BaseEntity, IAggregateRoot
{
    public Address Address { get; private set; }
    
    public int? BuyerId { get; private set; }
    
    private readonly List<OrderItem> _orderItems;
    public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
    
    // todo:
}

仓储(Repository)

IRepository泛型仓储,提供了一系列基础方法,配合规约使用。

public class GetTodoByIdQueryHandler : IRequestHandler<GetTodoByIdQuery, TodoResponse>
{
    private readonly IRepository<Todo> _todoRepository;
    public GetTodoByIdQueryHandler(IRepository<Todo> todoRepository)
    {
        _todoRepository = todoRepository ?? throw new ArgumentNullException(nameof(todoRepository));
    }
    
    public async Task<TodoResponse> Handle(GetTodoByIdQuery request, CancellationToken cancellationToken
    {
        // todo:
        
        // _todoRepository.ListAsync
        // _todoRepository.AddAsync
        // _todoRepository.UpdateAsync
        // _todoRepository.DeleteAsync
        // _todoRepository.GetByIdAsync
        // ......
        
        return response;
    }
}

规约(Specification)

使用ardalis/Specification实现。

public sealed class TodoSpec : Specification<Todo>
{
    // 默认
    public TodoSpec()
    {
        Query.OrderByDescending(x => x.Id);
    }
    
    // 支持条件过滤
    public TodoSpec(string filter)
    {
        if (!string.IsNullOrEmpty(filter))
        {
            Query.Where(x => x.Name.Contains(filter));
        }
        Query.OrderByDescending(x => x.Id);
    }
    
    // 支持条件过滤和分页
    public TodoSpec(string filter, int skip, int take)
    {
        if (!string.IsNullOrEmpty(filter))
        {
            Query.Where(x => x.Name.Contains(filter));
        }
        Query.OrderByDescending(x => x.Id).Skip(skip).Take(take);
    }
}

使用:

await _todoRepository.ListAsync(new TodoSpec());
await _todoRepository.ListAsync(new TodoSpec("xx"));
await _todoRepository.ListAsync(new TodoSpec("xx", 0, 10));

领域服务(Domain Service)

// todo:

领域事件(Domain Event)

基于MediatR实现。

public class NewUserEvent : BaseEvent
{
    public string Email { get; }
    
    // todo...
    
    public NewUserEvent(string email)
    {
        Email = email;
    }
}
public class User : BaseEntity, IAggregateRoot
{
    public string UserName { get; }
    
    public string Password { get; }
    
    public string Email { get; }
    
    // todo...
    
    public User(string userName, string password, string email)
    {
        UserName = userName;
        Password = password;
        Email = email;
        
        AddDomainEvent(new NewUserEvent(email)); //add event
    }
}
public class NewUserSendEmailHandler : INotificationHandler<NewUserEvent>
{
    public async Task Handle(NewUserEvent notification, CancellationToken cancellationToken)
    {
        // todo:
        // send email...
    }
}

业务异常(Business Exception)

// todo:

应用层(Application Layer)

命令查询分离(CQRS)

基于MediatR实现。

  • 以下的 Handler 相当于应用服务(Application Service)

  • CommandQuery 相当于 输入DTO

  • Response 相当于 输出DTO

Command
public class CreateTodoCommand : IRequest<TodoResponse>
{
    public string Name { get; set; }
    public string Desc { get; set; }
    
    // todo:
}
public class CreateTodoCommandHandler : IRequestHandler<CreateTodoCommand, TodoResponse>
{
    public async Task<TodoResponse> Handle(CreateTodoCommand request, CancellationToken cancellationToken)
    {
        // todo:
        
        return response;
    }
}
public class TodosController : ControllerBase
{
    private readonly IMediator _mediator;
    public TodosController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    [ProducesResponseType(StatusCodes.Status201Created)]
    public async Task<ActionResult<TodoResponse>> CreateTodo(CreateTodoCommand createTodoCommand)
    {
        var response = await _mediator.Send(createTodoCommand);
        return CreatedAtAction(nameof(GetTodo), new {id = response.Id}, response);
    }
}
Query
public class GetTodoByIdQuery : IRequest<TodoResponse>
{
    public int Id { get; set; }
}
public class GetTodoByIdQueryHandler : IRequestHandler<GetTodoByIdQuery, TodoResponse>
{
    public async Task<TodoResponse> Handle(GetTodoByIdQuery request, CancellationToken cancellationToken
    {
        // todo:
        
        return response;
    }
}
public class TodosController : ControllerBase
{
    private readonly IMediator _mediator;
    public TodosController(IMediator mediator)
    {
        _mediator = mediator;
    }
    
    [HttpGet("{id:int}")]
    public async Task<ActionResult<TodoResponse>> GetTodo(int id)
    {
        var response = await _mediator.Send(new GetTodoByIdQuery(id));
        return Ok(response);
    }
}

权限(Permissions)

使用[Permission]标记。

[Permission(nameof(CreateTodoCommand), "创建Todo", "Todo管理")]
public class CreateTodoCommand : IRequest<TodoResponse>
{
    public string Name { get; set; }
    public string Desc { get; set; }
    
    // todo:
}

工作单元(Unit Of Work)

泛型仓储IRepository中的Add,Update,Delete等方法默认实现了UOW

也可以将一个Request标记为[UnitOfWork],这样会开启事务,保证请求期间的所有操作在一个UOW中。

[UnitOfWork]
public class CreateTodoCommand : IRequest<TodoResponse>
{
    public string Name { get; set; }
    public string Desc { get; set; }
    
    // todo:
}

当前用户(Current User)

使用ICurrentUser服务。

public class GetTodoByIdQueryHandler : IRequestHandler<GetTodoByIdQuery, TodoResponse>
{
    private readonly ICurrentUser _currentUser;
    public GetTodoByIdQueryHandler(ICurrentUser<Todo> currentUser)
    {
        _currentUser = currentUser ?? throw new ArgumentNullException(nameof(currentUser));
    }
    
    public async Task<TodoResponse> Handle(GetTodoByIdQuery request, CancellationToken cancellationToken
    {
        // todo:
        
        // _currentUser.IsAuthenticated
        // _currentUser.Id
        // _currentUser.UserName
        // ......
        
        return response;
    }
}

Request 验证(Validation)

使用FluentValidation实现。

public class CreateTodoCommandValidator : AbstractValidator<CreateTodoCommand>
{
    public CreateTodoCommandValidator()
    {
        RuleFor(x => x.Name).NotEmpty().WithMessage("名称不能为空");
    }
}

DTO映射(Object Mapping)

使用AutoMapper实现。

public class DomainToResponseProfile : Profile
{
    public DomainToResponseProfile()
    {
        CreateMap<Todo, TodoResponse>();
    }
}
public class GetTodoByIdQueryHandler : IRequestHandler<GetTodoByIdQuery, TodoResponse>
{
    private readonly IMapper _mapper;
    public GetTodoByIdQueryHandler(IMapper mapper)
    {
        _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
    }
    
    public async Task<TodoResponse> Handle(GetTodoByIdQuery request, CancellationToken cancellationToken
    {
        // todo:
        
        return _mapper.Map<TodoResponse>(todo);
    }
}

致敬