asp.net-corerepositorydomain-driven-designspecification-pattern

DDD pass filters through multiple layers


In my onion architecture I've my PresentationLayer who contains a class named LogRabbitMQFilters with differents properties to filter search.

LogRabbitMQFilters

I pass LogRabbitMQFilters in ApplicationLayer by mapper :

    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.....*/
    }

In ApplicationLayer I map my logRabbitMQFilters to RabbitMQFilters which is declare in Persistance layer and I call my repository like this :

    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); 
    }

It's best approch to do this ? Is there another way to pass my filters class to the repository ? I thought of specification pattern but I don't now if it's a good solution.


Solution

  • DDD and onion architecture share common principles (e.g. domain isolation), however they also draw some different aspects regarding development techniques. It should be pointed out that architectures and designs should serve our goals rather than be the goals themselves.

    From your description it seems you have a CRUD-style system. I see no business rules, no domain concepts, no specifications. And that is, of course, not a bad thing.

    Isolating domain from other layers (presentation, infrastructure) is beneficial in particular when sophisticated validations are to be applied and business rules are to be enforced within complex entity objects. In your case, however, you map plain object LogRabbitMQFilters (presentation layer) to "itself" (application layer) then to plain object RabbitMQFilters (infrastructure layer). Passing an object as is from presentation layer to application layer is OK in cases like yours (even from a DDD/onion perspective), however:

    1. The second mapping should not exist, since infrastructure layer knows domain and therefore should receive your (presumably) domain entity LogRabbitMQFilters.
    2. LogRabbitMQFilters is actually not a true domain entity since, as mentioned before, it does not apply any business rule.
    3. Flatly mapping objects from one layer to another seems pointless.

    I thought of specification pattern but I don't now if it's a good solution

    Specification pattern is very useful when we want to pack expressions as business invariants, e.g. having a class named ShortMessage (business invariant) encapsulating an expression such as Message.Length < 42. But with your case I see no use for that, due to the CRUD nature of your application: you simply receive some user properties to function as operands in the context of an ORM object representing a database table, in order to do something like that:

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

    Each of the predicates separated by 'and' operator can be considered as specification, however such specifications are just technical, as they do not convey any business invariant. The object filter can actually be a client request.

    To conclude, it is acceptable to develop a single-layer application directly using client properties as operands for database filter expressions, as long as business invariants are out of picture (or at least with low complexity). If you would still like to have a DDD framework, i.e. having an application service (where you may apply simple validations such as DateFin > DateDebut) and a repository object together with the controller object, then I would recommend to have a single class "walking through" all three objects