'In Hexagonal architecture, can a service rely on another service, or is that tight coupling?

I am creating a banking application. Currently, there is one primary service, the transaction service. This service allows getting transactions by id, and creating transactions. In order to create a transaction, I first want to check if the transaction is valid, by checking the balance of the account it is trying to deduct from, and seeing if they have enough balance. Right now I am doing

TransactionController calls TransactionService. TransactionService creates Transaction, then checks if this is a valid transaction. At this point, I have created an AccountsService, that queries the AccountsRepository, returns an Account. I then do the comparison based on Account.balance > Transaction.amount.

I am conscious here that the TransactionService create method is relying on the AccountService get method. Additionally, AccountService is never directly called from a controller.

Is this an ok way to architect, or is there a more elegant way to do this?



Solution 1:[1]

Your logic seems fine. If you only need the Account Service from the Transaction service (or another Service), this is valid. No need to call an Account Service from a Controller just to do so if the logic makes no sense. In fact some Services that are invoked from a Controller may call many other services - such as an Email Service, a Text Message Service, and so on.

Solution 2:[2]

In your case, I would say it is ok because I guess that if Account.balance < Transaction.amount you don't really want to go forward with the transaction. So you must at some point get the needed data from the AccountService, there is no way around that.

If you just wanted o trigger some side-effect task (like sending an email or something) you could rely on an event-based approach on which TransactionService would publish an event and a hypothetical NotificationsService would react to it at some point in time and do its thing.

Solution 3:[3]

You can reference your AccountRepository directly in your TransactionService.

Sorry I don't speak Java but here is a C# example :

public class Transaction {
  // implementation redacted

  public Transaction(decimal amount, Account from, Account to) {
    if(amount > from?.Balance) throw ... ;
    // redacted
  }
}
public class TransactionService {
  private readonly AccountRepository accounts; // by injection
  private readonly TransactionRepository transactions; // by injection

  public void AddTransaction(decimal amount, int source, int destination) {
    var from = accounts.Find(source); // throws if not found
    var to = accounts.Find(destination); // throws if not found
    var transaction = new Transaction(amount, from, to);
    transactions.Insert(transaction);
    transactions.Persist();
  }
}

However, this solution is less ORM friendly because of the Transaction constructor. Another way around would be to use Account as your root aggregate, and place the business rule validation and entities relationship handling code there :

public class Account {
  // implementation redacted

  public void AddTransaction(decimal amount, Account to) {
    if(amount > this.Balance) throw ... ;
    // more redacted validations
    this.Debitus.Add(new Transaction { Amount = amount, From = this, To = to });
  }
}
public class TransactionService {
  private readonly AccountRepository accounts; // by injection

  public void AddTransaction(decimal amount, int source, int destination) {
    var from = accounts.Find(source); // throws if not found
    var to = accounts.Find(destination); // throws if not found
    from.AddTransaction(amount, to);
    accounts.Persist();
  }
}

Solution 4:[4]

I wouldn't create an account service. I would call the account repo from the transaction service. Apart from that, i wouldnt create a transaction object before knowing if it is valid. I would check the conditions before creating the transaction.

Solution 5:[5]

Agree with @choquero70 I don't like to compose or couple Serivce's operations because that "getBalance" maybe is different for one purpose or another, I would implement on TransactionService invoking AccountRepository or whatever repo or client that you need. TransactionService is the "perspective" of the operation

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 smac2020
Solution 2 João Dias
Solution 3 ArwynFr
Solution 4 choquero70
Solution 5 towies