Usually, when I create a domain entity (for example a bank account) it includes things like account id
and starting balance
.
But I have seen some Hexagonal projects that also have functions like calculateBalance
or createAccount
.
I usually put these functions it ports though.
My question is, what are the rules for deciding where to put the domain specific functions between the ports and domain entities?
As a rule of thumb, domain entities should contain domain-specific functions that operate on properties or make calculations based on properties without the need for external data to be involved in decision making, for example:
class Account {
withdraw(amount: number) {
const newBalance = this.balance - amount;
if (newBalance < 0) {
throw Error("Withdrawal denied");
}
this.balance = newBalance;
}
calculateFutureBalance() {
return this.balance - this.futureDebits;
}
}
class WithdrawUseCase {
execute(input) {
// TODO: run the following code under a single unit-of-work
const account = this.accountRepository.get(input.accountId);
account.withdraw(input.amount);
this.accountRepository.save(account);
}
}
If, on the other hand, you want to make business decisions based on external data, you should create that logical unit as a function inside a service ("domain service") and have your port call this function. For example:
class AccountService {
centralBankService: CentralBankService; // Assumed to be initialized
makeAccountGolden(account: Account) {
const hasCredit = this.centralBankService.hasCredit(this);
if (!hasCredit) {
throw Error("No credit from central bank");
}
account.makeGolden();
}
}
class MakeAccountGoldenUseCase {
execute(input) {
// TODO: run the following code under a single unit-of-work
const account = this.accountRepository.get(input.accountId);
this.accountService.makeAccountGolden(account);
this.accountRepository.save(account);
}
}