Let's say there are two classes, one is the user class, which contains the user information; the other one is the payment transaction class. The scenario is simple, if the user's age is > 65, create type A payment transaction; otherwise, create type B payment transaction.
There are several ways to do this:
- Create a method not belongs to user nor transaction, just call CreateTransaction. The logic is stated in this method:
func CreateTransaction(user, transaction) {
if user.GetAge() > 65:
transaction.CreateA()
else:
transaction.CreateB()
}
- The other option is the create a method for the user class:
class User {
...
func CreateTransaction(transaction) {
if user.GetAge() > 65:
transaction.CreateA()
else:
transaction.CreateB()
}
}
Then there is a CreateTransactionController method that calls the function like:
func CreateTransactinController(user, transaction) {
user.CreateTransaction()
}
My question is, is the option 1 considered as procedural programming since the logic is actually not owned by any object? (Or anemic pattern?) Is the difference between 1 and 2 be just different place to put the logic?
Thanks!
Since you marked this question as DDD, I will answer how a model, driven by the Domain, would implement this.
The question to answer is whether a
Transactionis enclosed within theUserobject. If it is enclosed, it means that you always go through the user's record to fetch transactions (and never access transactions directly). If a transaction by itself has a lifecycle, can be accessed directly, controls other parts of the domain, and so on, it cannot be enclosed within aUserand is a full-blown aggregate.Enclosing the
transactionwithin theuserwould mean that the user owns transaction-related operations, so option 2 would be the right way.If
transactionis a different aggregate, you could use aDomain Service(like your option 1), but it is incorrect because you handle two aggregates (userandtransaction) simultaneously. You are better off enclosing this functionality within theTransactionaggregate.The next question to address is how you would decide the type of transaction. One way is:
This is commonly how you handle changes that depend on attributes from multiple aggregates. You go ahead and change the state of the aggregate in the system, but check at a later point in time for the related aggregate data, and reverse the change if things are not congruent.
A better way is to create a
Specificationwhose explicit task is to derive the right payment type based on the user's age. The specification encloses your business logic (> 65), gives context to the age-driven requirement, and acts as a central place where you would control the logic.You can read more about specifications here: https://martinfowler.com/apsupp/spec.pdf