How to Refactor an Order Service for Better Maintainability and Testability
This article walks through a simple e‑commerce order flow, highlights the drawbacks of tightly coupled service code, and demonstrates step‑by‑step refactorings—introducing repository abstractions, domain entities, an anti‑corruption layer, and a generic message producer—to achieve a clean, extensible, and testable backend architecture.
Explanation
This simple example uses an e‑commerce order scenario to illustrate how to write good business code.
The case is a simplified simulation and may differ from real‑world situations.
Scenario: a user selects a product, adds it to the cart, and then places an order.
Select product
Place order
Checkout
Generate order
Notify
The business logic for submitting an order is as follows:
Validate account legality
Call third‑party API to get discounted price
Deduct wallet amount
Generate order information
Notify user of successful order
Code Implementation
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private UserMapper userMapper;
@Autowired
private ProductMapper productMapper;
@Autowired
private OrderMapper orderMapper;
@Autowired
private KafkaTemplate kafkaTemplate;
/**
* Purchase product and submit order
* @param userId User ID
* @param productId Product ID
*/
public Result submit(Long userId, Long productId) throws BizException {
// Validate account
UserDO userDO = userMapper.findById(userId);
if (userDO == null) {
throw BizException(USER_NOT_EXISTS);
}
// Get product and discount info
ProductDO productDO = productMapper.findById(productId);
Double delta = HttpUtils.getDiscount(productId);
double actualPayment = productDO.getPrice() - delta;
Money money = userDO.getMoney();
if (actualPayment > money.getRemain()) {
// Insufficient balance
return Result.fail("余额不足");
}
// Deduct amount
double remain = money.getRemain() - actualPayment;
money.setRemain(remain);
// Update wallet balance
userMapper.update(userDO);
// Generate order
OrderDO orderDO = new OrderDO();
orderDO.setUserId(userId);
orderDO.setProductId(productId);
orderMapper.save(orderDO);
// Notify user
kafkaTemplate.send("orderTopic", orderDO);
return Result.ok();
}
}Although the code works, it suffers from several issues as the business evolves:
Poor maintainability:
MyBatis mappers embed data‑access details in the service; changing the persistence technology forces changes in business code.
Data objects (DO) are tightly coupled to table structures; schema changes affect the service.
Direct calls to third‑party APIs (HttpUtils) make the service brittle when signatures change.
Direct use of KafkaTemplate ties the service to a specific MQ implementation.
Poor extensibility:
Additional discounts or promotional rules require frequent modifications.
Business logic is strongly dependent on the data storage structure.
Poor testability:
Direct dependencies on database, third‑party services, and middleware make isolated testing difficult.
Code Optimization 1
Introduce repository interfaces to isolate data access from business logic.
public interface UserRepository {
User findById(Long userId);
}Repositories return domain objects, not data objects, and decouple the service from a specific persistence mechanism.
Code Optimization 2
Encapsulate behavior inside domain entities to avoid anemic models.
public class Money {
private double remain;
public double getRemain() { return remain; }
public void setRemain(double remain) { this.remain = remain; }
/**
* Charge the specified amount.
*/
public boolean charge(double delta) {
if (remain < delta) {
return false;
}
this.remain -= delta;
return true;
}
}Code Optimization 3
Add an anti‑corruption (adapter) layer for third‑party discount services.
/** Anti‑corruption layer */
@Service
public class PayServiceImpl implements PayService {
@Autowired
private DiscountFacade discountFacade;
/**
* Pay for a product using the user's money.
*/
public boolean pay(Money money, Product product) {
Double delta = discountFacade.getDiscount(product.getId());
return money.charge(product.getPrice() - delta);
}
}Code Optimization 4
Abstract the message producer to avoid direct dependency on a concrete MQ implementation.
public interface MessageProducer<T, R> {
Result<R> send(T message);
}Summary
The optimized code combines the above improvements:
@Autowired
private UserRepository userRepository;
@Autowired
private ProductRepository productRepository;
@Autowired
private OrderRepository orderRepository;
@Autowired
private MessageProducer<Order, Result> messageProducer;
@Autowired
private PayService payService;
/**
* Purchase product and submit order
*/
public Result submit(Long userId, Long productId) throws BizException {
// Validate user
User user = userRepository.findByUserId(userId);
if (user == null) {
throw BizException(USER_NOT_EXISTS);
}
// Pay
Product product = productRepository.findById(productId);
boolean success = payService.pay(user.getMoney(), product);
if (!success) {
return Result.fail("费用扣除失败");
}
// Update account
userRepository.update(user);
// Create order
Order order = OrderFactory.create(user, product);
orderRepository.add(order);
// Notify user
messageProducer.send(order);
return Result.ok();
}The key goals are:
Independence from any specific framework.
Independence from UI presentation.
Independence from underlying data sources (MySQL, Oracle, MongoDB, etc.).
Independence from external dependencies; core business logic remains stable.
Testability: business logic can be verified without real databases, UI, or external services.
Source: https://www.toutiao.com/i6903053083555807752/
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITFLY8 Architecture Home
ITFLY8 Architecture Home - focused on architecture knowledge sharing and exchange, covering project management and product design. Includes large-scale distributed website architecture (high performance, high availability, caching, message queues...), design patterns, architecture patterns, big data, project management (SCRUM, PMP, Prince2), product design, and more.
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.
