Julien Martin
Julien Martin

Reputation: 421

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.

Upvotes: 2

Views: 679

Answers (2)

desertech
desertech

Reputation: 1029

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

Upvotes: 1

Md Farid Uddin Kiron
Md Farid Uddin Kiron

Reputation: 22495

In regards of complexity it always great to break up the application according to its responsibilities or concerns.

Considering your code, it seems you are following the industry specified standard. For better clarity I am also adding here a sample code snippet of a standard project architecture.

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

Note: Hope above steps guided you accordingly. Additionally, you could also have a look our official document for more information regarding industry practices.

Upvotes: 1

Related Questions