I decided to code my new project in Clean Architecture, with which I'm not very familiar. I'm using this template https://github.com/JasonGT/CleanArchitecture
I have an Entity Withdrawal.cs
which has a BTCAddress ReceiveAddress
property:
public class Withdrawal
{
///ctor...
public Guid Id { get; private set; }
public decimal FiatAmountRequested { get; private set; }
public Currency FiatCurrency { get; private set; }
public BTCAddress ReceiveAddress { get; private set; }
}
I extracted BTCAddress
into a ValueObject because a BTC address isn't an ordinary string, there is validation logic needed.
BTCAddress.cs
public class BTCAddress
{
public BTCAddress(string address)
{
Address = address;
}
public string Address { get; private set; }
public override string ToString()
{
return Address;
}
}
As you can see, I haven't implemented any validations on the address. I must use an external library in order to validate. My question is can I add a dependency in BTCAddress
on let's say IBTCAddressValidator
and implement this interface in the Infrastructure layer, or it isn't permitted to have dependencies on interfaces in the Domain Layer?
If not I guess I should have the dependency of this interface in every Use Case (CreateWithdrawalRequestUseCase for example) that has to create a BTCAddress
object. Or create a CreateBTCAddressUseCase
and let other use cases be depended on it, which doesn't sound right to me. Or create some kind of a helper class for creating this value object.
What is the right way to add validation from external library in my value object?
EDIT:
Here is my RequestWithdrawalCommand
using System;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using DepositWithdraw.Application.Common.Interfaces;
using DepositWithdraw.Domain.Entities;
using DepositWithdraw.Domain.ValueObjects;
using FluentValidation;
using MediatR;
namespace DepositWithdraw.Application.Withdrawals.Commands.RequestWithdrawal
{
public class RequestWithdrawalCommand : IRequest, IMapTo<Withdrawal>
{
public string ExternalId { get; set; }
public DateTime ExternalDateTime { get; set; }
public Client Client { get; set; }
public decimal FiatAmountRequested { get; set; }
public FiatCurrency FiatCurrency { get; set; }
public URL CallbackUrl { get; set; }
public BTCAddress ReceiveAddress { get; set; }
public class RequestWithdrawalCommandValidator : AbstractValidator<RequestWithdrawalCommand>
{
public RequestWithdrawalCommandValidator(IBTCAddressValidator btcAddressValidator)
{
RuleFor(p => p.FiatAmountRequested)
.GreaterThan(0)
.WithMessage("Requested fiat amount must be positive.");
RuleFor(p => p.ReceiveAddress)
.Must(address => address != null && btcAddressValidator.IsValid(address.ToString()))
.WithMessage("Invalid BTC address.");
}
}
public class RequestWithdrawalCommandHandler : AsyncRequestHandler<RequestWithdrawalCommand>
{
private readonly IApplicationDbContext _db;
private readonly IRatesRepository _ratesRepository;
private readonly IBTCWalletService _BTCWalletService;
private readonly IMapper _mapper;
public RequestWithdrawalCommandHandler(
IApplicationDbContext db,
IRatesRepository ratesRepository,
IBTCWalletService BTCWalletService,
IMapper mapper)
{
_db = db;
_ratesRepository = ratesRepository;
_BTCWalletService = BTCWalletService;
_mapper = mapper;
}
protected override async Task Handle(RequestWithdrawalCommand request, CancellationToken cancellationToken)
{
var btcRate = _ratesRepository.GetBtcRate(request.Client.Id, request.FiatCurrency.ToString());
var btcAmount = request.FiatAmountRequested / btcRate.BuyRate;
var withdrawal = _mapper.Map<Withdrawal>(request);
await _BTCWalletService.SendBTC(request.ReceiveAddress, btcAmount);
}
}
}
}
This code works as expected, The ReceiveAddress
is validated, but I don't like the fact that I would have to repeat this validation process everywhere I'm using BTCAddress.cs
. In this case I don't see the benefit from extracting it in a ValueObject, I can achieve the same thing keeping the ReceiveAddress
as a string.