Refactoring a Billing Service with a Responsibility‑Chain Architecture for Better Extensibility
The article explains why a complex billing module in a settlement system needs optimization, describes the problems of monolithic code and tangled responsibilities, and demonstrates a refactor using the responsibility‑chain pattern with clear, pluggable nodes, accompanied by Java code examples and best‑practice recommendations.
Billing is the core business of a settlement system, responsible for calculating fees such as goods payment, logistics, and platform commission after a business system pushes order data. As the number of fee items grows, the original implementation became hard to maintain, with a bulky billing method that mixed validation, core calculation, persistence, clearing and callbacks.
The refactor aims to make the core logic highly cohesive, plug‑in‑able, and easily extensible. By extracting common steps (pre‑check, model translation, persistence, clearing) and isolating the actual fee calculations, the process can be orchestrated through a responsibility‑chain, reducing coupling and improving readability.
Using the responsibility‑chain pattern, each processing step is implemented as a separate handler. The chain is configured in BillingHandlerChainConfig , where handlers for pre‑check, translation, rule loading, calculation (itself a sub‑chain for logistics, commission, promotion, and goods), persistence, and event publishing are added in order.
/**
* 订单计费服务
*/
public class OrderBillingService {
private Locker locker;
/**
* 计费
* @param order 订单
* @param event 订单事件
*/
public void billing(OrderDTO order, OrderEvent event) {
// 1.预校验
if (!"ORDER_SIGN".equals(event.getEventType())) {
return;
}
// 2.并发检查
if (!locker.tryLock(order.getOrderNumber())) {
return;
}
// 3.幂等检查
BillingOrder billingOrder = getByOrderNumber(order.getOrderNumber());
if (billingOrder != null) {
return;
}
// 4.模型转换
BillingDTO billingDTO = translate(order);
MerchantDTO seller = getSeller(order.getSellerId());
// 5.加载计费规则
Contract contract = loadBillingContract(billingDTO, seller);
Promotion promotion = loadBillingPromotion(billingDTO, seller);
// 6.开始结算计费
BigDecimal orderAmount = billingDTO.getOrderAmount(); // 订单金额
BigDecimal billingAmount = BigDecimal.ZERO;
BigDecimal logisticsFeeTotal = billingDTO.getLogisticsFee(); // 订单物流费
List
orderSkuList = billingDTO.getSkuList(); //计费明细
for (BillingSkuDTO billingSkuDTO : orderSkuList) {
BigDecimal skuBillingAmount = billingSkuDTO.getAmount(); // 明细计费金额
// 6.1.计算物流费
BigDecimal logisticsFee = billingSkuDTO.getAmount()
.divide(orderAmount, 6, BigDecimal.ROUND_HALF_UP)
.multiply(logisticsFeeTotal); // 示例按金额占比分摊物流费,实际分摊比这要复杂
FeeDTO logisticsFeeDTO = new FeeDTO();
logisticsFeeDTO.setFeeType("logistics");
logisticsFeeDTO.setFee(logisticsFee);
logisticsFeeDTO.setAmount(logisticsFee);
billingSkuDTO.addFee(logisticsFeeDTO);
skuBillingAmount = skuBillingAmount.subtract(logisticsFee);
// 6.2.计算佣金
BigDecimal commissionFeeRate = contract.getCommissionFeeRate();
BigDecimal commissionFee = billingSkuDTO.getAmount().multiply(commissionFeeRate);
FeeDTO commissionFeeDTO = new FeeDTO();
commissionFeeDTO.setFeeType("commission");
commissionFeeDTO.setFee(commissionFee);
commissionFeeDTO.setAmount(commissionFee);
billingSkuDTO.addFee(commissionFeeDTO);
skuBillingAmount = skuBillingAmount.subtract(commissionFee);
// 6.3.计算货款
billingSkuDTO.setBillingAmount(skuBillingAmount);
}
// 7.开始优惠计费
for (BillingSkuDTO billingSkuDTO : orderSkuList) {
BigDecimal skuBillingAmount = billingSkuDTO.getBillingAmount(); // 明细计费金额
List
feeDTOList = billingSkuDTO.getFeeList();
for (FeeDTO feeDTO : feeDTOList) {
// 该费用可优惠
if (promotion.isPromotion(feeDTO, billingDTO)) {
// 获取优惠金额
BigDecimal promotionFee = promotion.getPromotionFee(feeDTO, billingDTO);
feeDTO.setPromotion(promotionFee);
feeDTO.setAmount(feeDTO.getAmount().subtract(promotionFee));
skuBillingAmount = skuBillingAmount.add(promotionFee);
}
}
// 重新计算货款
billingSkuDTO.setBillingAmount(skuBillingAmount);
billingAmount = billingAmount.add(skuBillingAmount);
}
billingDTO.setBillingAmount(billingAmount);
// 8.持久化
billingOrder = save(billingDTO);
// 9.清分
clearing(billingOrder);
// 10.计费完成
publish(billingOrder);
}
} public class BillingHandlerChainConfig {
public BillingHandlerChain getOrderBillingHandlerChain() {
BillingHandlerChain orderChain = new OrderBillingHandlerChain();
List
handlers = new ArrayList<>();
// 1.校验
handlers.add(new PreCheckBillingHandler());
// 2.模型转换
handlers.add(new TranslateBillingHandler());
// 3.计费规则
handlers.add(new RuleBillingHandler());
// 4.计费
CalculateBillingHandler calculateBillingHandler = new CalculateBillingHandler();
handlers.add(calculateBillingHandler);
List
calculateHandlers = new ArrayList<>();
calculateBillingHandler.setHandlers(calculateHandlers);
// 4.1.物流费计算
calculateHandlers.add(new LogisticsBillingHandler());
// 4.2.佣金计算
calculateHandlers.add(new CommissionBillingHandler());
// 4.3.优惠计算
calculateHandlers.add(new PromotionBillingHandler());
// 4.4.货款计算
calculateHandlers.add(new GoodsBillingHandler());
// 5.持久化
handlers.add(new SaveBillingHandler());
// 6.发布事件
handlers.add(new PublishBillingHandler());
orderChain.setHandlers(handlers);
return orderChain;
}
}
public class OrderBillingService {
private BillingHandlerChain orderBillingHandlerChain;
private void billing(OrderDTO order, OrderEvent event) {
BillingContext context = initContext(order, event);
orderBillingHandlerChain.handle(context);
}
}
public class OrderBillingHandlerChain implements BillingHandlerChain {
private List
handlers;
@Override
public void setHandlers(List
handlers) { this.handlers = handlers; }
@Override
public boolean handle(BillingContext context) {
if (handlers == null) { throw new IllegalStateException("订单计费没有可用流程"); }
for (BillingHandler handler : handlers) {
if (!handler.handle(context)) { return false; }
}
return true;
}
}
public class CalculateBillingHandler implements BillingHandler, BillingHandlerChain {
private List
handlers;
@Override
public void setHandlers(List
handlers) { this.handlers = handlers; }
@Override
public boolean handle(BillingContext context) {
if (handlers == null) { throw new IllegalStateException("计费子链没有可用流程"); }
for (BillingHandler handler : handlers) {
if (!handler.handle(context)) { return false; }
}
return true;
}
}
public class LogisticsBillingHandler implements BillingHandler {
@Override
public boolean handle(BillingContext context) {
BillingDTO billingDTO = context.getBillingOrder();
BigDecimal orderAmount = billingDTO.getOrderAmount(); // 订单金额
BigDecimal logisticsFeeTotal = billingDTO.getLogisticsFee(); // 订单物流费
List
orderSkuList = billingDTO.getSkuList(); //计费明细
for (BillingSkuDTO billingSkuDTO : orderSkuList) {
BigDecimal skuBillingAmount = billingSkuDTO.getBillingAmount(); // 明细计费金额
BigDecimal logisticsFee = billingSkuDTO.getAmount()
.divide(orderAmount, 6, BigDecimal.ROUND_HALF_UP)
.multiply(logisticsFeeTotal);
FeeDTO logisticsFeeDTO = new FeeDTO();
logisticsFeeDTO.setFeeType("logistics");
logisticsFeeDTO.setFee(logisticsFee);
logisticsFeeDTO.setAmount(logisticsFee);
billingSkuDTO.addFee(logisticsFeeDTO);
skuBillingAmount = skuBillingAmount.subtract(logisticsFee);
billingSkuDTO.setBillingAmount(skuBillingAmount);
}
return true;
}
}
public class PromotionBillingHandler implements BillingHandler {
@Override
public boolean handle(BillingContext context) {
BillingDTO billingDTO = context.getBillingOrder();
List
orderSkuList = billingDTO.getSkuList(); //计费明细
Promotion promotion = context.getPromotion();
// 开始优惠计费
for (BillingSkuDTO billingSkuDTO : orderSkuList) {
BigDecimal skuBillingAmount = billingSkuDTO.getBillingAmount(); // 明细计费金额
List
feeDTOList = billingSkuDTO.getFeeList();
for (FeeDTO feeDTO : feeDTOList) {
// 该费用可优惠
if (promotion.isPromotion(feeDTO, billingDTO)) {
// 获取优惠金额
BigDecimal promotionFee = promotion.getPromotionFee(feeDTO, billingDTO);
feeDTO.setPromotion(promotionFee);
feeDTO.setAmount(feeDTO.getAmount().subtract(promotionFee));
skuBillingAmount = skuBillingAmount.add(promotionFee);
}
}
billingSkuDTO.setBillingAmount(skuBillingAmount);
}
return true;
}
}By moving clearing and upstream notifications out of the billing module and triggering them only via a final "publish billing completed" event, the refactor keeps the billing domain highly cohesive. The article concludes with reflections on code decay, recommending coding standards, regular refactoring, and disciplined design to mitigate technical debt.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.