DDD in real project 1
DDD in real project
understand DDD
Data Model in data layer,Domain Model in domain layer, Repository is the one connect6 these two。
Adapter(controller scheduler consumer)
——– > VO(View Object)
|
App(service executor)
————-> DTO(Data Transfer Object)
| |
| Domain(gateway model ability)
————-> Entiry
| Infratructure(gateway impl , mapper, config)
————-> DO
- DDD structure
-
xxx-application
-
xxx-common
-
xxx-domain
-
xxx-infrastructure
-
xxx-interfaces
-
xxx-rpc
STEP1: use PRC to test first
RPC
Dubbo – implements Serializable
Create a table first to test CRUD operation.
1. Create a activity table
CREATE TABLE
activity
2. POM file configuration
-
xxx-application,应用层,引用:domain
-
xxx-common,通用包,引用:无
-
xxx-domain,领域层,引用:infrastructure
-
xxx-infrastructure,基础层,引用:无
-
xxx-interfaces,接口层,引用:application、rpc,
-
xxx-rpc,RPC接口定义层,引用:common
interface的package 是 war, 还需要用 starter-web , 别的都是 jar
3. Mybatis
-
put related dependencie in lottery-interfaces pom.xml
-
yml file
4. Dubbo
- put related dependencie in lottery-interfaces pom.xml
- yml file
5. Define and develop RPC interface
lottery/rpc structure
- dto
- ActivityDto implements Serializable
- mapping to mybatis
- ActivityDto implements Serializable
- req
- ActivityReq implements Serializable
- (use activityId as example): get set
- res
- ActivityRes implements Serializable
- Field (Result, ActivityDto) : Result from common package, including String 200 code
- IActivityBooth.java
- interface
- method: return ActivityRes; pemerater(ActivityReq req)
- ActivityBooth
+ @Service
+ ActivityBooth.java
+ @Resource
+ private IActivityDao activityDao;
+ @Override
+ public ActivityRes queryActivityById(ActivityReq req)
+ return new ActivityRes
@Service is comimg from org.apache.dubbo.config.annotation.Service, it means this class would be managed by Dubbo
6. Build a test project and call RPC
Test RPC
-
install first, the root and rpc module Lifecycle need install first
-
ApiTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApiTest {
private Logger logger = LoggerFactory.getLogger(ApiTest.class);
@Reference(interfaceClass = IActivityBooth.class, url = "dubbo://127.0.0.1:20880")
private IActivityBooth activityBooth;
@Test
public void test_rpc() {
ActivityReq req = new ActivityReq();
req.setActivityId(100001L);
ActivityRes result = activityBooth.queryActivityById(req);
logger.info("result:{}", JSON.toJSONString(result));
}
}
- Data Object, only used to map to the table
- Entity, dont need serialize and persistent
- DTO, lies in the input and output parameters that adapt to different business scenarios
DDD model another structure
-
lottery-common:公共包
-
lottery-interfaces:接口层,依赖公共包lottery-common
- 功能 : 处理用户发送的restful请求或者解析用户输入的配置文件,将信息传递给应用层
- 服务组成 : 接口服务
-
lottery-domain:领域层,不依赖其他任何模块
- 功能 :操作转换领域中的跨实体或者值对象
- 服务组成 : 领域服务 - –1)封装核心业务逻辑
-
lottery-infrastructure:基础设施层,依赖领域层lottery-domain
- 功能 : 提供资源服务,数据库,缓存邓,降低外部资源对系统业务逻辑的影响,为系统解耦
- 服务组成 : 基础服务 – 仓储服务 –1)实现方式IOC 2)功能: 领域服务和应用服务通过调用仓储服务,实现持久化数据对象或者直接访问资源
-
lottery-application:应用层,向上依赖接口层lottery-interfaces、向下依赖领域层lottery-domain和基础设施层lottery-infrastructure
- 功能 :1. 应用和用户行为 2. 负责服务的组合编排转发 3.处理业务用例的执行顺序和结果的拼装
- 服务组成 : 1. 应用服务 : 对基础层的缓存,文件等数据直接出操作。2. 领域事件服务 : 领域事件发布和订阅,通过事务总线和消息队列进行异步数据传输,实现服务直接解耦
-
lottery-bootstrap: 启动(打包),只依赖应用层lottery-application(因为应用层会整合掉其他所有的模块包)
-
xxx-application,应用层,引用:domain
-
xxx-common,通用包,引用:无
-
xxx-domain,领域层,引用:infrastructure
-
xxx-infrastructure,基础层,引用:无
-
xxx-interfaces,接口层,引用:application、rpc,
-
xxx-rpc,RPC接口定义层,引用:common
STEP2: design database and tables
-
in business level; the lottery system, as a link in the marketing activity platform, takes over the link of the activity playing method, bonus consumption, prize distribution and other systems to help the entire business complete user activity.
-
as a part of strategic design, need consider segregation principle; activities, algorithms, rules, policies, users, orders and other fields.
-
Introduce table design; according to the definition of each module in the domain driver, design the database table, which corresponds to the activity table, lottery policy configuration table, access rule engine table, user lottery ticket record table, and the data structure of these tables Other tables that run, such as: record the number of user participation, etc.
A lottery drawing process
- run a customer lottery
-
query activity bill
- query activity info –> SQL
Activity
- query user take activity account –> SQL
UserTakeActivityCount
- encapsulation –>
ActivityBillVO
- query activity info –> SQL
-
auth info
- verify info status
- verify info time
- verify info product quentity stock
- verify users’s remaining participation times
-
Product Quantity Stock decrease –> SQL
Activity
-
record users activity info –> partition, declarative transaction management
-
reduce users' participate time
- user first time participate –> SQL
UserTakeActivityCount
- user non-first time participate –> SQL
UserTakeActivityCount
- user first time participate –> SQL
-
insert redeem award info –> use
UUID
as the only index, to provent redeem the award repeatlyuuid = uid + "_" + acvitityId + "_" + userTakeActivity.getTakeCount()
–> SQLUserTakeActivity
-
-
sql encapsulation –> strategy id , activity id
-
lottery.sql
database lottery
tables
activity
award
strategy
strategy_detail
database lottery_01
tables
user_take_activity (分库不分表)
user_take_activity_count (分库不分表)
user_strategy_export_001 (分库分表)
user_strategy_export_002
…
database lottery_02
tables
user_take_activity
user_take_activity_count
user_strategy_export_001
user_strategy_export_002
…
database and tables design thinking:
-
Access frequency: For frequently accessed data, Data size: For a large amount of data, Data type, different types of data, Data range, it can be stored in a separate database or table to improve read and write performance.
-
The main purpose of partition is: data sharing, improving QPS/TPS, sharing pressure, and improving scalability.
For example, if the read and write performance of the database is degraded, or the amount of data in a single table is too large, then you need to consider partition.
avoiding the performance bottleneck of a single database and improving the performance and scalability of the system. can also solve the limitation of database storage capacity and improve the storage capacity of the database.
In addition, the cost of database management and maintenance will also increase. Therefore, when considering partition, you need to carefully weigh the pros and cons to determine whether it is really necessary to perform partition operations
STEP3: lottery strategy module
主要是奖品的发放流程
- award
- model
- req
- GoodsReq
- 奖品发货信息
- award id, award name, award content, orderId, user id, shippingaddress, String extInfo
- res
- DistributionRes
- 商品配送结果
- user id, 编码, 描述, 结算单ID
- vo
- ShippingAddress
- req
- repository
- impl
- IAwardRepository
- 对分库分表中的用户中奖纪录操作
- AwardRepository
- service
-
factory
-
factory
- GoodsConfig
- 各类发奖奖品配置类
- 为了工厂发配做准备, 所以这个类要放入3个分配方法
- @Resource
- private DescGoods descGoods
- …
- 把几个分配方式放入map里面调用
- Map<Integer, IDistributionGoods> goodsMap = new ConcurrentHashMap<>()
- goodsMap.put(Constants.AwardType.DESC.getCode(), descGoods);
- DistributionGoodsFactory
- IDistributionGoods getDistributionGoodsService(Integer awardType)
- return goodsMap.get(awardType)
- GoodsConfig
-
-
goods
- impl
- CouponGoods
- // 更新用户领奖结果
- getDistributionGoodsName()
- return Constants.AwardType.PhysicalGoods.getCode()
- return Constants.AwardType.RedeemCodeGoods.getCode()
- DesGoods
- PhysicalGoods
- RedeemGoods
- CouponGoods
- IDistributionGoods
- DistributionRes doDistribution(GoodsReq req)
- getDistributionGoodsName();
- DistributionBase
- 更新分库分表中,用户个人的抽奖记录表中奖品发奖状态
- void updateUserAwardState(String uId, String orderId, String awardId, Integer awardState, String awardStateInfo)
- impl
-
- model
STEP4: modify DDD
-
domain
- activity
- model
- repository
- service
- award
- model
- repository
- IAwardRepository
- service
- strategy
- model
- repository
- IStrategyRepository
- service
- activity
-
infrastruture
-
dao
- IStrategyDao.java
- IStrategyDetail.java
- IAwardDao.java
-
po
- Award.java
- StrategyDetail.java
- Strategy.java
-
repository
- AwardRepository
import cn.itedus.lottery.domain.award.repository.IAwardRepository; @Component public class AwardRepository implements IAwardRepository { }
- StrategyRepository
@Component public class StrategyRepository implements IStrategyRepository {
-
STEP5: activity
the domain layer domain defines the storage interface, and the base layer infrastructure implements the respository interface.
The functions that the activity domain layer needs to provide include: activity creation, activity status processing, and user claim activity operation.
The operation of event creation mainly uses transactions, because when the event system provides the operation background to create an event, it needs to include: event information, prize information, policy information, policy details, and other additional extended content, all of which need to be under one transaction Carry out storage.
Review of activity status, [1 Edit, 2 Review, 3 Cancel review, 4 Pass, 5 Run (worker scan status after review passed), 6 Reject, 7 Close, 8 Open], here we will use the state in the design mode mode is processed.
- domain
-
activity
-
repository
- IActivityRepository
-
service
- deploy
-
impl
- ActivityDeployImpl
// 这个类需要引入.activity.model 中vo, aggregates的类
public class ActivityDeployImpl implements IActivityDeploy { @Resource private IActivityRepository activityRepository; @Transactional(rollbackFor = Exception.class) @Override public void createActivity(ActivityConfigReq req) { } public void updateActivity(ActivityConfigReq req) { // TODD } }
-
IActivityDeploy
public interface IActivityDeploy { void createActivity(ActivityConfigReq req); void updateActivity(ActivityConfigReq req); }
-
- partake
- stateflow
- deploy
-
model
- vo
- ActivityVO
- private Integer state
- AlterStateVO
- /** 活动ID */
- private Long activityId;
- /** 更新前状态 */
- private Integer beforeState;
- /** 更新后状态 */
- private Integer afterState;
- AwardVO
- StrategyDetailVO
- StrategyVO
- ActivityVO
- aggregates
- ActivityConfigRich
- import model.vo.ActivityVO
- import model.vo.AwardVO;
- import model.vo.StrategyVO;
- construction (ActivityVO activity, StrategyVO strategy, List
awardList)
- req
- ActivityConfigReq
- Long activityId
- ActivityConfigRich ctivityConfigRich
- ActivityConfigReq
- vo
-
-
- ActivityDeployImpl
public class ActivityDeployImpl implements IActivityDeploy {
@Resource
private IActivityRepository activityRepository;
@Transactional(rollbackFor = Exception.class)
@Override
public void createActivity(ActivityConfigReq req) {
ActivityConfigRich activityConfigRich = req.getActivityConfigRich();
try {
// 添加活动配置
ActivityVO activity = activityConfigRich.getActivity();
activityRepository.addActivity(activity);
// 添加奖品配置
List<AwardVO> awardList = activityConfigRich.getAwardList();
activityRepository.addAward(awardList);
// 添加策略配置
// 添加策略详细配置
}
}
}
+ stateflow逻辑解释
arraignment, checkPass, checkRefuse, checkRevoke, close, open, doing
- stateflow
- IStateHandler : provide outer service
- AbstractState
public abstract class AbstractState {
@Resource
protected IActivityRepository activityRepository;
//活动提审
public abstract Result arraignment(Long activityId, Enum<Constants.ActivityState> currentState);
//审核通过
public abstract Result checkPass(Long activityId, Enum<Constants.ActivityState> currentState);
// 审核拒绝
public abstract Result checkRefuse(Long activityId, Enum<Constants.ActivityState> currentState);
// 撤审撤销
public abstract Result checkRevoke(Long activityId, Enum<Constants.ActivityState> currentState);
// 活动关闭
public abstract Result close(Long activityId, Enum<Constants.ActivityState> currentState);
// 活动开启
public abstract Result open(Long activityId, Enum<Constants.ActivityState> currentState);
// 活动执行
public abstract Result doing(Long activityId, Enum<Constants.ActivityState> currentState);
}
状态接口,因为比如拒绝了后后面的状态不同,所以就需要有不同的状态的类
- stateflow
- IStateHandler : provide outer service
- AbstractState
- event
- CloseState extend AbstractState
- 7个状态
- 优化掉原本需要在各个流程节点中的转换使用 ifelse 的场景,这样操作以后也可以更加方便你进行扩展。当然其实这里还可以使用如工作流的方式进行处理
- StateConfig : 把状态放入map里面,状态流转配置抽象类
@Resource private ArraignmentState arraignmentState; .... protected Map<Enum<Constants.ActivityState>, AbstractState> stateGroup = new ConcurrentHashMap<>(); PostConstruct public void init() { stateGroup.put(Constants.ActivityState.ARRAIGNMENT, arraignmentState); stateGroup.put(Constants.ActivityState.CLOSE, closeState);
- impl
- StateHandlerImpl extends StateConfig implements IStateHandler
@Override public Result arraignment(Long activityId, Enum<Constants.ActivityState> currentStatus) { return stateGroup.get(currentStatus).arraignment(activityId, currentStatus); }
activity test
用一个 init() 方法里面,new 一个 ActivityVO activity,. StrategyVO , StrategyDetailVO strategyDetail_01 , StrategyDetailVO strategyDetail_02 设置好相关信息
用test_createActivity(), activityDeploy.createActivity(new ActivityConfigReq(activityId, activityConfigRich));
模板抽奖流程
STEP6: ID generate
- ID generate
- distribution
- snowflake
- pros
- Long type, eary to ooperate,It is 64-bit long, it is half the size of UUIDs
- cons
- pros
- Redis ID
- pros
- not rely on database
- sorted id
- cons
- need Redis
- In order to generate a unique id, it is troublesome to maintain a Redis cluster
- pros
- snowflake
- nonDistribution
- UUID
- pros
- easy
- Unique across every table, every database, every server.
- Allows easy merging of records from different databases.
- Allows easy distribution of databases across multiple servers
- cons
- non-sequential
- stored as string, can have bad query performance
- a bit more storage needed
- unreadable
- pros
- Sequential ID
-
pros
- easy
- Predictable
-
cons
- primary keys are very often exposed to the user
- Different database migrations
- only generated by master server has the risk of a single point of failure
-
- UUID
- distribution