0

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?

Vinn
  • 1,030
  • 5
  • 13

1 Answers1

0

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);
  }
}
desertech
  • 911
  • 3
  • 7
  • This is extremely clear and exactly what I wanted to know. Thank you desertech! In the case of withdraw, is it a domain service that calls this function too? As in, the withdraw skips the port and appears in the service when needed? – Vinn May 07 '23 at 04:22
  • In the withdraw use-case domain logic is completely encapsulated within entity, hence no need for domain-service. I added more code for clarification. – desertech May 07 '23 at 17:40