搭建查券公众号的微服务拆分原则:Java领域驱动设计(DDD)划分查券/用户/券码限界上下文的实践
大家好,我是 微赚淘客系统3.0 的研发者省赚客!
在“查券公众号”系统中,初期单体架构难以应对高并发查券、复杂用户等级体系与动态券码管理等需求。为提升可维护性与扩展性,我们采用领域驱动设计(DDD)思想,将系统划分为三个核心限界上下文(Bounded Context):用户上下文(User Context)、查券上下文(CouponSearch Context) 和 券码上下文(Voucher Context),并通过事件驱动实现上下文间解耦。
限界上下文划分与职责定义
- 用户上下文:负责用户注册、登录、等级、积分、标签等身份属性管理。
- 查券上下文:接收用户查券请求,调用第三方联盟 API 获取商品优惠信息,不存储券码。
- 券码上下文:管理实际可用的隐藏券、高佣券、限时券等实体,包含库存、有效期、领取状态等。
各上下文独立数据库、独立部署单元,通过 REST 或消息队列通信。
用户上下文核心模型
// juwatech.cn.user.domain.model.User.java
package juwatech.cn.user.domain.model;
public class User {
private String userId;
private String openId;
private UserTier tier; // 普通/VIP/企业
private int points;
public boolean isEligibleForHighCommission() {
return tier == UserTier.VIP || tier == UserTier.ENTERPRISE;
}
// getters/setters
}
// juwatech.cn.user.application.UserService.java
package juwatech.cn.user.application;
import juwatech.cn.user.domain.model.User;
public interface UserService {
User getUserByOpenId(String openId);
void awardPoints(String userId, int points);
}
查券上下文聚合根与服务
查券上下文不持有券码,仅作为查询协调器:
// juwatech.cn.search.domain.service.CouponSearchService.java
package juwatech.cn.search.domain.service;
import juwatech.cn.search.domain.model.SearchRequest;
import juwatech.cn.search.domain.model.CouponResult;
public interface CouponSearchService {
CouponResult search(SearchRequest request);
}
// juwatech.cn.search.application.CouponSearchAppService.java
package juwatech.cn.search.application;
import juwatech.cn.user.client.UserClient; // Feign Client
import juwatech.cn.search.domain.service.CouponSearchService;
import org.springframework.stereotype.Service;
@Service
public class CouponSearchAppService {
private final UserClient userClient;
private final CouponSearchService searchService;
public CouponSearchAppService(UserClient userClient, CouponSearchService searchService) {
this.userClient = userClient;
this.searchService = searchService;
}
public Object handleSearch(String openId, String itemId) {
var user = userClient.getUserByOpenId(openId);
var request = new juwatech.cn.search.domain.model.SearchRequest(itemId, user.getUserId());
return searchService.search(request);
}
}
券码上下文实体与库存控制
券码是独立资源,支持领取与核销:
// juwatech.cn.voucher.domain.model.Voucher.java
package juwatech.cn.voucher.domain.model;
import java.time.LocalDateTime;
public class Voucher {
private String voucherId;
private String itemId;
private String code;
private LocalDateTime expireAt;
private boolean claimed;
private String claimedBy;
public boolean canBeClaimed() {
return !claimed && LocalDateTime.now().isBefore(expireAt);
}
public void claim(String userId) {
if (!canBeClaimed()) {
throw new IllegalStateException("Voucher not available");
}
this.claimed = true;
this.claimedBy = userId;
}
}
// juwatech.cn.voucher.application.VoucherService.java
package juwatech.cn.voucher.application;
import juwatech.cn.voucher.domain.model.Voucher;
import juwatech.cn.voucher.domain.repository.VoucherRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class VoucherService {
private final VoucherRepository voucherRepository;
public VoucherService(VoucherRepository voucherRepository) {
this.voucherRepository = voucherRepository;
}
@Transactional
public String claimVoucher(String voucherId, String userId) {
Voucher voucher = voucherRepository.findById(voucherId);
voucher.claim(userId);
voucherRepository.save(voucher);
return voucher.getCode();
}
}
跨上下文通信:事件驱动解耦
当用户完成查券,若存在专属券码,由查券上下文发布事件,券码上下文监听并预加载:
// juwatech.cn.search.infrastructure.event.CouponSearchedEvent.java
package juwatech.cn.search.infrastructure.event;
public class CouponSearchedEvent {
private String userId;
private String itemId;
private boolean hasExclusiveVoucher;
// constructor/getters
}
// juwatech.cn.voucher.infrastructure.listener.VoucherPreloadListener.java
package juwatech.cn.voucher.infrastructure.listener;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
@Component
public class VoucherPreloadListener {
private final juwatech.cn.voucher.application.VoucherPreloader preloader;
public VoucherPreloadListener(juwatech.cn.voucher.application.VoucherPreloader preloader) {
this.preloader = preloader;
}
@KafkaListener(topics = "coupon-searched")
public void onCouponSearched(juwatech.cn.search.infrastructure.event.CouponSearchedEvent event) {
if (event.hasExclusiveVoucher()) {
preloader.preloadForUser(event.getUserId(), event.getItemId());
}
}
}
API 网关路由与上下文隔离
Spring Cloud Gateway 按路径路由:
# gateway routes
spring:
cloud:
gateway:
routes:
– id: user–service
uri: lb://user–service
predicates:
– Path=/api/user/**
– id: search–service
uri: lb://search–service
predicates:
– Path=/api/search/**
– id: voucher–service
uri: lb://voucher–service
predicates:
– Path=/api/voucher/**
通过 DDD 的限界上下文划分,查券公众号系统实现了高内聚、低耦合的微服务架构,各团队可独立开发、测试与部署,显著提升交付效率。
本文著作权归 微赚淘客系统3.0 研发团队,转载请注明出处!
网硕互联帮助中心


评论前必须登录!
注册