skip to Main Content

Currently I’m facing a trouble implementing the business logic using clean architecture and cqrs pattern. I’m using Mediatr and AutoMapper to do that. I have the following structure for the cqrs pattern

  • ApplicationCore
    • DTOs
      • SmartphoneDto.cs
    • Features
      • Smartphone
        • Handlers
          • Commands
            • AddSmartphoneCommandHandler.cs
          • Queries
        • Requests
          • Commands
            • AddSmartphoneCommand.cs
          • Queries
  • DomainCore
    • Smartphone.cs

And the following classes:

SmartphoneDto.cs

public class SmartphoneDto
{
    public int Id { get; set; }
    public string serialnumber { get; set; }
}

Domain Entity Smartphone.cs

public class Smartphone
{
    public int Id { get; set; }
    public string imei { get; set; }
    public string serialnumber { get; set; }
    public string calculatedValue { get; set; }

}

AddSmartphoneCommand.cs

public class AddSmartphoneCommand : IRequest<bool>
{
    public SmartphoneDto SmartphoneDto { get; set; }
}

AddSmartphoneCommandHandler.cs

public class AddSmartphoneCommandHandler : IRequestHandler<AddSmartphoneCommand, bool>
{
    private readonly ISmartphoneRepository _smartphoneRepository;
    private readonly IMapper _mapper;

    public AddSmartphoneCommandHandler(ISmartphoneRepository smartphoneRepository, IMapper mapper)
    {
        _smartphoneRepository = smartphoneRepository;
        _mapper = mapper;
    }

    public async Task<bool> Handle(AddSmartphoneCommand request, CancellationToken cancellationToken)
    {
        var smartphone = _mapper.Map<Smartphone>(request.SmartphoneDto);
        //Business Logic???
        await _smartphoneRepository.AddAsync(smartphone);

        return true;
    }
}

And I´m not sure where the business logic goes, I’ve read different post with suggestions like these but with no possible solutions or a clear example:

Post 1 As I understood we can put business logic in a external class that works as a service called from the handle method of the command handler

Post 2 But in this post shows that the command handler doesn’t have to contain any logic.

Due to my confusion, I’ve thinked put the business logic as follow:

{
    private readonly ISmartphoneRepository _smartphoneRepository;
    private readonly IMapper _mapper;

    public AddSmartphoneCommandHandler(ISmartphoneRepository smartphoneRepository, IMapper mapper)
    {
        _smartphoneRepository = smartphoneRepository;
        _mapper = mapper;
    }

    public async Task<bool> Handle(AddSmartphoneCommand request, CancellationToken cancellationToken)
    {
        var smartphone = _mapper.Map<Smartphone>(request.SmartphoneDto);

        #region businesslogic
        //All the business logic in this place
        smartphone.imei = someApi.GetAsync("someurl", smartphone.serialnumber);
        smartphone.calculatedValue = (smartphone.serialnumber - smartphone.imei) * 0.05;
        //end business logic
        #endregion

        await _smartphoneRepository.AddAsync(smartphone);

        return true;
    }
}

What would be the correct place in the structure to put that kind of business logic?

2

Answers


  1. Commands and Queries should be under the Application Layer.

    DEPENDENCIES BETWEEN LAYERS:

    • Domain layer:
      Doesn’t depend on any layer.
      Contains: Entities, Aggregates, Value Objects, Repositories contracts.
    • Application layer:
      Depends on the Domain and Infrastructure layers.
      Contains: CQRS, Use Cases, API contracts & implementations.
    • Infrastructure layer:
      Depends on the Domain layer.
      Contains: Repositories implementation, ORMs (e.g., Entity Framework), Cryptography, Logging, etc.
    • Presentation layer:
      Depends on the Application layer.
    Login or Signup to reply.
  2. The best place for business logic is in the domain layer itself.

    Your domain objects are currently what we call "anemic" models, meaning they have no encapsulation whatsoever (just a bag of properties with getters and setters that don’t enforce any business rules).

    It sounds like setting the imei and perhaps the serialnumber should update the calculatedValue. The code can be moved to the domain by encapsulating the logic in a computed property, like so:

    public class Smartphone
    {
        public int Id { get; set; }
        public string serialnumber { get; set; }
        public string imei { get; set; }
        public string calculatedValue => (this.serialnumber - this.imei) * 0.05;
    }
    

    This is one way to do it. There is a lot more you could encapsulate into the Smartphone class but that’s a start.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search