Question

I have doubts about the way the domain should enforce business rules when there is more than one aggregate involved.

Suppose I have the account and external account aggregates:

public class Account {
    public String getId() {...}
    public void add (Double amount) {}
}

public class ExternalAccount {
    public String getId() {...}
    public void add (Double amount) {}
}

and this service:

public class TransferService implements TransferServiceInterface {
    public void transfer (String AccountId, String ExternalAccountId, Double amount) {
        Account fromAccount = accRepository.get(AccountId);
        ExternalAccount toAccount = extAccRepository.get(ExternalAccountId);

        transferIsValid(fromAccount, toAccount, amount);

        fromAccount.add(-amount);
        toAccount.add(amount);
    }
}

transferIsValid will throw an exception if the transfer doesn't comply with the domain rules.

How can I prevent the the user of this model from not using the Service and execute something like this:

    Account fromAccount = accRepository.get(AccountId);
    ExternalAccount toAccount = extAccRepository.get(ExternalAccountId);

    fromAccount.add(-amount);
    toAccount.add(amount);

The user didn't use the service and didn't use transferIsValid(...) to check the integrity. I believe that there is an error in my design because the user shouldn't be able to do something invalid. How can I prevent it? Where is the error in my design?

Was it helpful?

Solution

First of all: Do not use Add() to withdraw. DDD is all about following the domain. And I don't think that you say So when I add a negative amount of money to account A, the equal amount will be added to account B when you talk to the product owner. Add a Widthdraw method.


Remember. No users are involved when coding. programmers are. And all programmers can screw up the code.

Regarding the service: there is nothing you can do to prevent that with code. Unless the only valid way to withdraw money is to transfer it to another account. In that case you can change the Widthdraw() method to take another account as the argument.

Other than that, simply add documentation to your Widthdraw method and say that the service should be used if two accounts are involved. imho any DDD developer should know that the service should be used since it's how we do things in DDD (you & I did, and so should also the next dev with DDD experience).

OTHER TIPS

Business logic should be in domain object, so, instead of putting business logic in TransferService, the better way, I think, to avoid business logic leak to Service is to create new Entity calledAccountTransfer with contains AccountFrom and AccountTo, something like (sorry I use C# in here):

public class AccountTransfer
{
    Account From { get; set; }
    Account To { get; set; }

    // More properties         

    private bool IsValid(ammount)
    {}

    public void DoTransfer(int amount)
    {
        is (IsValid(ammount))
        {
            From.Withdraw(amount);
            To.Add(amount);
        }
    }
}

You might need more information in object AccountTransfer like:

  1. When to transfer
  2. What kind of transfer: transfer via visa, paypal....
  3. To populate this class into database, you store transfer history to trace them later.

With this way, you also put IsValid method inside AccountTransfer as private method.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top