【问题标题】:DDD pass filters through multiple layersDDD 通过多层过滤器
【发布时间】:2021-11-18 14:56:37
【问题描述】:

在我的洋葱架构中,我的 PresentationLayer 包含一个名为 LogRabbitMQFilters 的类,该类具有用于过滤搜索的不同属性。

我通过映射器在 ApplicationLayer 中传递 LogRabbitMQFilters :

    public RabbitMQController(IELKService iELKService, IMapper mapper)
    {
        _iELKService = iELKService;
        _mapper = mapper;
    }

    public async Task<IActionResult> Index(int? id, LogRabbitMQFilters filters)
    {
        var filtersMapped = _mapper.Map<LogRabbitMQFilters>(filters);

        var response = await _iELKService.GetLog(filtersMapped);

        /*some code.....*/
    }

在 ApplicationLayer 中,我将 logRabbitMQFilters 映射到在 Persistance 层中声明的 RabbitMQFilters,然后像这样调用我的存储库:

    public ELKService(IELKRepository iELKRepository, IMapper mapper)
    {
        _iELKRepository = iELKRepository;
        _mapper = mapper;
    }

    public async Task<LogResult> GetLog(LogRabbitMQFilters  logRabbitMQFilters)
    {
        var filterMapped = _mapper.Map<RabbitMQFilters>(logRabbitMQFilters);

        return await _iELKRepository.GetLogs(filterMapped); 
    }

这是最好的方法吗?还有另一种方法可以将我的过滤器类传递给存储库吗?我想到了规范模式,但如果它是一个好的解决方案,我现在没有。

【问题讨论】:

  • 我认为您的architecture 似乎还不错。实践是Controller,它将实现Service Layer Interface,然后Service Layer 将调用Repository,它将具有Database Read-Write 操作或任何其他操作。所以你的设计应该是 1. LogRabbitMQFilterController, 2. ILogRabbitMQFilterService ,3. LogRabbitMQFilterService 4. ILogRabbitMQFilterRepository , 5. LogRabbitMQFilterRepository
  • @MdFaridUddinKiron 所以你想为过滤器添加另一个控制器,否则我不理解你的答案:)
  • 可能是我没有解释清楚,我不确定我在哪里添加了两个控制器?一个Controller、一个IServiceIRepository就是这样。
  • 你可以have a look code sample here 摆脱进一步的困惑
  • 你说得对,这是我的错...谢谢:)

标签: asp.net-core repository domain-driven-design specification-pattern


【解决方案1】:

DDD 和洋葱架构具有共同的原则(例如域隔离),但是它们也绘制了一些关于开发技术的不同方面。应该指出的是,架构和设计应该服务于我们的目标,而不是目标本身。

从你的描述看来你有一个 CRUD 风格的系统。我看不到任何业务规则,没有领域概念,没有规范。当然,这并不是一件坏事。

将域与其他层(表示、基础设施)隔离是有益的,特别是当要应用复杂的验证并且要在复杂的实体对象中执行业务规则时。但是,在您的情况下,您将普通对象LogRabbitMQFilters(表示层)映射到“自身”(应用程序层),然后再映射到普通对象RabbitMQFilters(基础设施层)。在像您这样的情况下(即使从 DDD/洋葱的角度来看),将对象按原样从表示层传递到应用程序层是可以的,但是:

  1. 第二个映射不应该存在,因为基础设施层知道域,因此应该接收您的(大概)域实体LogRabbitMQFilters
  2. LogRabbitMQFilters 实际上不是真正的域实体,因为如前所述,它不应​​用任何业务规则。
  3. 将对象从一层平面映射到另一层似乎毫无意义。

我想到了规范模式,但如果它是一个好的解决方案,我现在不考虑

当我们想要将表达式打包为业务不变量时,规范模式非常有用,例如有一个名为ShortMessage(业务不变)的类,它封装了一个表达式,例如Message.Length &lt; 42。但是对于您的情况,由于您的应用程序的 CRUD 特性,我认为这样做没有用:您只需接收一些用户属性以在表示数据库表的 ORM 对象的上下文中用作操作数,以便执行类似的操作:

myORMManager
    .FetchAll<MyTableObject>()
    .Filter(record =>
        record.DateDebut == filter.DateDebut &&
        record.DateFin == filter.DateFin &&
        .
        .
        record.Message == filter.Message
        .
        .
        .
    );

由“and”运算符分隔的每个谓词都可以被视为规范,但是这些规范只是技术性的,因为它们不传达任何业务不变性。对象filter实际上可以是客户端请求。

总之,直接使用客户端属性作为数据库过滤器表达式的操作数来开发单层应用程序是可以接受的,只要业务不变量不适用(或至少具有低复杂性)。如果您仍然希望拥有一个 DDD 框架,即拥有一个应用程序服务(您可以在其中应用简单的验证,例如 DateFin > DateDebut)和一个存储库对象以及控制器对象,那么我建议您使用一个类“walking”通过"所有三个对象

【讨论】:

  • 感谢您的意见,我完全同意 LogRabbitMQFilters 不是真正的域实体,可以删除第二个映射。如果我理解正确,我会删除第二个映射,并在应用层使用客户端属性作为数据库过滤器表达式的操作数。所以我想在我的IELKRepository 中我可以创建一个这样的方法:public Task&lt;LogRabbitMQ&gt; GetLogWithFilters(Expression&lt;Func&lt;T, bool&gt;&gt; predicate); 在应用层我会用我的不同过滤器(logRabbitMQFilters)调用 GetLogWithFilters 对吗?
  • 既然我们已经同意您的应用程序中的表达式不代表业务规范,那么在IELKRepository 中创建它们会更有意义。应用层应将LogRabbitMQFilters 传递给存储库,而不是将其转换为技术基础架构表达式。
【解决方案2】:

考虑到复杂性,拆分应用程序总是很好 根据其职责或关注点。

考虑到您的代码,您似乎遵循了行业指定的标准。为了更清楚起见,我还在此处添加了标准项目架构的示例代码 sn-p。

Controller:

[Route("api/User")]
[ApiController]
public class UserController : ControllerBase
{
    private readonly IUserService _userService;

    public UserController(IUserService userService)
    {
        _userService = userService;
    }
    [HttpGet]
    [Authorize]
    [Route("users")]
    public async Task<ActionResult> Users()
    {
        var users = await _userService.GetAllUsers();
        return Ok(new ResponseViewModel { output = "success", msg = "request successful", returnvalue = users });
    }
    
}

Service Interface:

public interface IUserService
    {
        Task<List<UserViewModel>> GetAllUsers();
    }

Service Implementation:

public  class UserService : IUserService
    {
        private readonly IUserRepository _userRepository;

        public UserService(IUserRepository userRepository)
        {
            _userRepository = userRepository;
        }

        public async Task<List<UserViewModel>> GetAllUsers()
        {
            return await _userRepository.GetAllUsers();
        }
    }

Repository Interface:

public interface IUserRepository
    {
        Task<List<UserViewModel>> GetAllUsers();
    }

Repository Implementation:

public class UserRepository : IUserRepository
    {
        private readonly AppDbContext _dbContext;
        private readonly IMapper _mapper;
        public UserRepository(AppDbContext dbContext, IMapper mapper)
        {
            _dbContext = dbContext;
            _mapper = mapper;

        }

        public async Task<List<UserViewModel>> GetAllUsers()
        {
            var users = await _dbContext.Users.ToListAsync();
            var userViewModel = _mapper.Map<List<UserViewModel>>(users);
            return userViewModel;

        }
    }

Model:

public class UserViewModel
    {
        public long user_id { get; set; }
        public string full_name { get; set; }
    }

注意:希望以上步骤能够相应地指导您。此外,您还可以查看我们的official document 了解更多信息 关于行业惯例。

【讨论】:

    猜你喜欢
    • 2014-07-26
    • 1970-01-01
    • 1970-01-01
    • 2020-05-04
    • 2019-06-19
    • 1970-01-01
    • 1970-01-01
    • 2018-11-27
    • 2015-11-06
    相关资源
    最近更新 更多