Is this considered as procedural programming (or anemic pattern)?

105 views Asked by At

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:

  1. 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()
    }
  1. 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!

1

There are 1 answers

0
Subhash On

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 Transaction is enclosed within the User object. 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 a User and is a full-blown aggregate.

Enclosing the transaction within the user would mean that the user owns transaction-related operations, so option 2 would be the right way.

If transaction is a different aggregate, you could use a Domain Service (like your option 1), but it is incorrect because you handle two aggregates (user and transaction) simultaneously. You are better off enclosing this functionality within the Transaction aggregate.

The next question to address is how you would decide the type of transaction. One way is:

  1. The API request would send the user's age as part of the request
  2. The controller calls a service and passes the user's age as an integer
  3. The service invokes a factory method that accepts age as an integer, initializes, and returns the correct type of transaction.
  4. The user's age may have changed in the backend by the time the request came through, or the request may have been incorrect. You handle this problem by having a "corrective policy," which runs after the payment transaction is created later. If the user's age matches the type of transaction chosen, then all is good. If it doesn't, the transaction is reversed.

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 Specification whose 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