'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 |