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
- Commands
- Requests
- Commands
- AddSmartphoneCommand.cs
- Queries
- Commands
- Handlers
- Smartphone
- DTOs
- 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
Commands and Queries should be under the Application Layer.
DEPENDENCIES BETWEEN LAYERS:
Doesn’t depend on any layer.
Contains: Entities, Aggregates, Value Objects, Repositories contracts.
Depends on the Domain and Infrastructure layers.
Contains: CQRS, Use Cases, API contracts & implementations.
Depends on the Domain layer.
Contains: Repositories implementation, ORMs (e.g., Entity Framework), Cryptography, Logging, etc.
Depends on the Application layer.
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 theserialnumber
should update thecalculatedValue
. The code can be moved to the domain by encapsulating the logic in a computed property, like so:This is one way to do it. There is a lot more you could encapsulate into the
Smartphone
class but that’s a start.