DDD in real project 1

Wed, Mar 1, 2023 8-minute read

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
  • 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));
    }

}

DTO DO, Entity

  • 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

  1. 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.

  2. as a part of strategic design, need consider segregation principle; activities, algorithms, rules, policies, users, orders and other fields.

  3. 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
    • 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
      • insert redeem award info –> use UUID as the only index, to provent redeem the award repeatly uuid = uid + "_" + acvitityId + "_" + userTakeActivity.getTakeCount() –> SQL UserTakeActivity

    • 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:

  1. 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.

  2. 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
    • 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)
      • goods

        • impl
          • CouponGoods
            • // 更新用户领奖结果
            • getDistributionGoodsName()
              • return Constants.AwardType.PhysicalGoods.getCode()
              • return Constants.AwardType.RedeemCodeGoods.getCode()
          • DesGoods
          • PhysicalGoods
          • RedeemGoods
        • IDistributionGoods
          • DistributionRes doDistribution(GoodsReq req)
          • getDistributionGoodsName();
        • DistributionBase
          • 更新分库分表中,用户个人的抽奖记录表中奖品发奖状态
          • void updateUserAwardState(String uId, String orderId, String awardId, Integer awardState, String awardStateInfo)

STEP4: modify DDD

  • domain

    • activity
      • model
      • repository
      • service
    • award
      • model
      • repository
        • IAwardRepository
      • service
    • strategy
      • model
      • repository
        • IStrategyRepository
      • service
  • 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
      • model

        • vo
          • ActivityVO
            • private Integer state
          • AlterStateVO
            • /** 活动ID */
            • private Long activityId;
            • /** 更新前状态 */
            • private Integer beforeState;
            • /** 更新后状态 */
            • private Integer afterState;
          • AwardVO
          • StrategyDetailVO
          • StrategyVO
        • 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

  • 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));

模板抽奖流程

image

通用业务结构

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
      • 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
    • 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
      • 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