hexagonal-architecture

How do I decide what goes into the entity and what goes into the port in Hexagonal Architecture?


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?


Solution

  • 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);
      }
    }