DDD congestion model code example

Keywords: Lombok Database Attribute

Anemia model

We generally use three-tier architecture for business development:

Repository + Entity
Service + BO(Business Object)
Controller + VO(View Object)

In the three-tier architecture business development, we often use the development mode based on the anemia model. Anemia model refers to that all business logic is placed in the service layer, and business objects only contain data but not business logic. Let's look at the code example.

/**
 * Account business object
 * 
 * @author Today's headline "IT fat Xu"
 *
 */
public class AccountBO {

    /**
     * Account ID
     */
    private String accountId;

    /**
     * Account balance
     */
    private Long balance;
    
    /**
     * Is it frozen?
     */
    private boolean isFrozen;

    public String getAccountId() {
        return accountId;
    }

    public void setAccountId(String accountId) {
        this.accountId = accountId;
    }

    public Long getBalance() {
        return balance;
    }

    public void setBalance(Long balance) {
        this.balance = balance;
    }

    public boolean isFrozen() {
        return isFrozen;
    }

    public void setFrozen(boolean isFrozen) {
        this.isFrozen = isFrozen;
    }
}

/**
 * Transfer business service realization
 * 
 * @author Today's headline "IT fat Xu"
 *
 */
@Service
public class TransferServiceImpl implements TransferService {

    @Autowired
    private AccountService accountService;

    @Override
    public boolean transfer(String fromAccountId, String toAccountId, Long amount) {
        AccountBO fromAccount = accountService.getAccountById(fromAccountId);
        AccountBO toAccount = accountService.getAccountById(toAccountId);

        /** Check transfer out account**/
        if (fromAccount.isFrozen()) {
            throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN);
        }
        if (fromAccount.getBalance() < amount) {
            throw new MyBizException(ErrorCodeBiz.INSUFFICIENT_BALANCE);
        }
        fromAccount.setBalance(fromAccount.getBalance() - amount);

        /** Check transfer in account**/
        if (toAccount.isFrozen()) {
            throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN);
        }
        toAccount.setBalance(toAccount.getBalance() + amount);

        /** Update database**/
        accountService.updateAccount(fromAccount);
        accountService.updateAccount(toAccount);
        return Boolean.TRUE;
    }
}

In the TransferServiceImpl implementation class is the anemia model development method. AccountBO has only data and no business logic. The whole code style tends to be process oriented, so some people call the anaemic model anti pattern.

 

2 hyperemia model

In the development mode of DDD based on congestion model, we introduce domain layer. Domain layer contains business object BO, but not only data. This layer also contains business logic. Let's look at code examples.

/**
 * Account business object
 * 
 * @author Today's headline "IT fat Xu"
 *
 */
public class AccountBO {

    /**
     * Account ID
     */
    private String accountId;

    /**
     * Account balance
     */
    private Long balance;

    /**
     * Is it frozen?
     */
    private boolean isFrozen;

    /**
     * Lending strategy
     */
    private DebitPolicy debitPolicy;

    /**
     * Accounting strategy
     */
    private CreditPolicy creditPolicy;

    /**
     * Lending method
     * 
     * @param amount Amount of money
     */
    public void debit(Long amount) {
        debitPolicy.preDebit(this, amount);
        this.balance -= amount;
        debitPolicy.afterDebit(this, amount);
    }

    /**
     * Transfer method
     * 
     * @param amount Amount of money
     */
    public void credit(Long amount) {
        creditPolicy.preCredit(this, amount);
        this.balance += amount;
        creditPolicy.afterCredit(this, amount);
    }

    public boolean isFrozen() {
        return isFrozen;
    }

    public void setFrozen(boolean isFrozen) {
        this.isFrozen = isFrozen;
    }

    public String getAccountId() {
        return accountId;
    }

    public void setAccountId(String accountId) {
        this.accountId = accountId;
    }

    public Long getBalance() {
        return balance;
    }

    /**
     * BO And DO conversion must add set method, which is a trade-off
     */
    public void setBalance(Long balance) {
        this.balance = balance;
    }

    public DebitPolicy getDebitPolicy() {
        return debitPolicy;
    }

    public void setDebitPolicy(DebitPolicy debitPolicy) {
        this.debitPolicy = debitPolicy;
    }

    public CreditPolicy getCreditPolicy() {
        return creditPolicy;
    }

    public void setCreditPolicy(CreditPolicy creditPolicy) {
        this.creditPolicy = creditPolicy;
    }
}


/**
 * Accounting strategy implementation
 * 
 * @author Today's headline "IT fat Xu"
 *
 */
@Service
public class CreditPolicyImpl implements CreditPolicy {

    @Override
    public void preCredit(AccountBO account, Long amount) {
        if (account.isFrozen()) {
            throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN);
        }        
    }

    @Override
    public void afterCredit(AccountBO account, Long amount) {
        System.out.println("afterCredit");
    }
}

/**
 * Implementation of lending strategy
 * 
 * @author The public number is "IT Xu fat".
 *
 */
@Service
public class DebitPolicyImpl implements DebitPolicy {

    @Override
    public void preDebit(AccountBO account, Long amount) {
        if (account.isFrozen()) {
            throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN);
        }
        if (account.getBalance() < amount) {
            throw new MyBizException(ErrorCodeBiz.INSUFFICIENT_BALANCE);
        }
    }

    @Override
    public void afterDebit(AccountBO account, Long amount) {
        System.out.println("afterDebit");
    }
}

/**
 * Transfer business service realization
 * 
 * @author Today's headline "IT fat Xu"
 *
 */
@Service
public class TransferServiceImpl implements TransferService {

    @Resource
    private AccountService accountService;
    @Resource
    private CreditPolicy creditPolicy;
    @Resource
    private DebitPolicy debitPolicy;

    @Override
    public boolean transfer(String fromAccountId, String toAccountId, Long amount) {
        AccountBO fromAccount = accountService.getAccountById(fromAccountId);
        AccountBO toAccount = accountService.getAccountById(toAccountId);
        fromAccount.setDebitPolicy(debitPolicy);
        toAccount.setCreditPolicy(creditPolicy);

        fromAccount.debit(amount);
        toAccount.credit(amount);
        accountService.updateAccount(fromAccount);
        accountService.updateAccount(toAccount);
        return Boolean.TRUE;
    }
}

Policy object is included in AccountBO, which can implement business logic. In this way, business logic is implemented in policy object, and process oriented code of service layer is reduced.


3 some thoughts

Some friends may say that the congestion mode is to put some businesses on the Domain layer, which is nothing special. I have these thoughts on this point of view:

(1) The code business style is more object-oriented rather than process oriented, and the whole logic becomes more cohesive and detailed.

(2) In the design principle, there is a principle of opening and closing: open for expansion and close for modification. I think this is the most important design principle. Many design patterns, such as strategy pattern and template method pattern, are designed based on this principle. The congestion model BO can contain a variety of policies, and policy patterns can be used to manage policies.

(3) The domain layer is not intended to replace the Service layer, but a supplement and enhancement. Although both the domain layer and the business layer contain business, they have different purposes. The business layer can combine businesses in different fields, and can add flow control, monitoring, log and permission control aspects, which is more abundant than the domain layer.

Now Lombok framework is very popular, making the code very concise. It should be noted that using Lombok at will may break the code encapsulation. For example, the AccountBO object should not expose the setBalance method, but the setBalance method must be exposed because each layer of object needs the attribute copy, which is also a trade-off strategy.

535 original articles published, praised 1162, and 4.5 million visitors+
His message board follow

Posted by Xproterg^vi on Thu, 06 Feb 2020 21:40:17 -0800