云计算百科
云计算领域专业知识百科平台

搭建查券公众号的微服务拆分原则:Java领域驱动设计(DDD)划分查券/用户/券码限界上下文的实践

搭建查券公众号的微服务拆分原则: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: userservice
uri: lb://userservice
predicates:
Path=/api/user/**
id: searchservice
uri: lb://searchservice
predicates:
Path=/api/search/**
id: voucherservice
uri: lb://voucherservice
predicates:
Path=/api/voucher/**

通过 DDD 的限界上下文划分,查券公众号系统实现了高内聚、低耦合的微服务架构,各团队可独立开发、测试与部署,显著提升交付效率。

本文著作权归 微赚淘客系统3.0 研发团队,转载请注明出处!

赞(0)
未经允许不得转载:网硕互联帮助中心 » 搭建查券公众号的微服务拆分原则:Java领域驱动设计(DDD)划分查券/用户/券码限界上下文的实践
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!