DDD Domain Driven Design of Qiqi Architecture -- event driven

Posted by jonki on Sun, 19 Dec 2021 05:13:10 +0100

preface

We have finished the related concepts and implementation of the domain layer. I believe you have a certain understanding of the concept of DDD domain layer. The domain layer needs to complete the core business logic, which is the most core part of DDD.
Let's start with the application layer.

  • The application layer is above the domain layer and is a very thin layer between the user interface layer and the domain layer. The application layer itself does not do specific business logic, and the business logic is encapsulated in the domain layer
  • The application layer only arranges business logic and assembles aggregation and aggregation services of multiple bounded contexts
  • Event driven portal configuration
  • Call of ACL anticorrosive coating
  • Call external middleware, MQ, KAFKA

Qiqi architecture requires all Application classes to implement Application interface to identify Application layer objects
Any operation initiated by the application layer to the domain layer has atomicity and persistence.
On the implementation of the Repository, the @ Transaction annotation has been added

Event driven - Definition

We know that DDD requires that the specific implementation of all services must be defined in the domain layer, and the application layer should splice the specific business logic. Some business logic calls do not need to be displayed, and the main business process does not need to pay attention to other business logic triggered by it, such as the following example:

For example, users A Transfer. After the user completes the transfer, a short message will be triggered to notify the user A. In this process, SMS notification does not belong to the main process of transfer, and the transfer task cannot be affected by the failure of SMS notification. Moreover, the transfer process does not care about the success of SMS notification. Therefore, in this process, it is necessary to send "transfer success" to the message bus as an event, and the corresponding listener is responsible for listening to the event, Then trigger the corresponding SMS sending operation

The business operations of a single entity are implemented within the business, the business operations of a single aggregation across multiple entities are implemented within the aggregation, the single bounded context and multiple aggregated operations are implemented within the domain service And these individual operations are transactional.

DDD requirements: operations that cross the boundary context cannot be called asynchronously in an event driven manner in a transaction. MQ and KAFKA can be regarded as cross application asynchronous event driven methods. How can they be implemented in the same application?

Tips: For the event driven pattern, it is recommended to learn about the listener pattern in the design pattern first

Spring provides the listener implementation, so we can extend the event driven implementation based on the listener implemented in spring.

Event driven implementation of Spring

  1. Throw the corresponding event at the appropriate business logic
@Component
public class CustomInfoService implements AggregateService {

    public void transCash(int cashMoney, CustomInfo customInfo1, CustomInfo customInfo2) {
        int customInfo1Cash = customInfo1.getUser().getCash();
        customInfo1.getUser().setCash(customInfo1Cash - cashMoney);
        int customInfo2Cash = customInfo2.getUser().getCash();
        customInfo2.getUser().setCash(customInfo2Cash + cashMoney);
        log.info(customInfo1.getUser() + "transTo " + customInfo2.getUser() + ",money:" + cashMoney);
        SpringContextUtil.publishEvent(EventFactory.build("transCash.success", "Transfer succeeded"));
    }

}

The implementation of SpringContextUtil:

    public static void publishEvent(ApplicationEvent event) {
        applicationContext.publishEvent(event);
    }
  1. Define the listening event handling class and implement the onApplicationEvent interface of ApplicationListener
@Slf4j
@Data
public class DddEventListener implements ApplicationListener {
    @Autowired
    private EventHandlerMethodMapping eventHandlerMethodMapping;
    @Autowired
    private EventResultHandlerList eventResultHandlerList;

    @Override
    @AsyncAnnotation
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof DddEvent) {
            ((EventSource) event.getSource()).setHandleTime(System.currentTimeMillis());
            Map<String, EventHandlerMethodMapping.BeanMethod> beanMethodMaps =
                    eventHandlerMethodMapping.getBeanMethodMaps();

            String eventType = ((EventSource) event.getSource()).getEventType();
            for (Map.Entry<String, EventHandlerMethodMapping.BeanMethod> stringBeanMethodEntry : beanMethodMaps.entrySet()) {
                String handlerType = stringBeanMethodEntry.getKey();
                EventHandlerMethodMapping.BeanMethod handler = stringBeanMethodEntry.getValue();
                Pattern p = Pattern.compile(handlerType);
                Matcher m = p.matcher(eventType);
                if (m.matches()) {
                    Object bean = handler.getBean();
                    Method method = handler.getMethod();
                    Object data = ((EventSource) event.getSource()).getData();
                    EventResult eventResult = EventResult.builder().beanType(bean.getClass())
                            .method(method).eventType(handlerType).build();
                    try {
                        Object invokeResult = method.invoke(bean, data);
                        eventResult.setSuccess(true);
                        eventResult.setResult(invokeResult);
                    } catch (Exception e) {
                        eventResult.setSuccess(false);
                        eventResult.setException(e);
                        log.error("event:" + event + "drive" + bean.getClass().getSimpleName() + "." + method.getName() + "fail", e);
                    }
                    List<EventResult> eventResults = ((EventSource) event.getSource()).getEventResults();
                    if (eventResults == null) {
                        eventResults = new ArrayList<>();
                        ((EventSource) event.getSource()).setEventResults(eventResults);
                    }
                    eventResults.add(eventResult);
                }
            }
            ((EventSource) event.getSource()).setFinishTime(System.currentTimeMillis());
            //Event result processing
            handleEventResult(event);
            if (log.isDebugEnabled()) {
                log.debug("event:" + event + "The driver completes the call.");
            }

        }
    }

    private void handleEventResult(ApplicationEvent event) {
        List<EventResultHandler> eventResultHandlers = eventResultHandlerList.getEventResultHandlers();
        for (EventResultHandler handler : eventResultHandlers) {
            try {
                handler.handle(event);
            } catch (Exception e) {
                log.error("When processing event results, the event handler<" + handler.getClass().getSimpleName() + ">Processing failed", e);
            }
        }
    }

    @PostConstruct
    private void registerMethods() {
        eventHandlerMethodMapping.registerMethods();
        eventResultHandlerList.registerMethods();
    }

}

  1. Scan all classes that implement the Application interface, and register and annotate the classes and methods of @ EventDrivenPattern
@Data
public class EventHandlerMethodMapping {

    private Map<String, BeanMethod> beanMethodMaps = new HashMap<>();

    public void registerMethods() {
        final String[] beanDefinitionNames = SpringContextUtil.getApplicationContext().getBeanDefinitionNames();
        for (String beanName : beanDefinitionNames) {
            Object beanName1 = SpringContextUtil.getBean(beanName);
            if (beanName1 == null) {
                continue;
            }
            if (Application.class.isAssignableFrom(beanName1.getClass())) {
                registerHandlerMethod(beanName);
            }
        }
    }


    private void registerHandlerMethod(String beanName) {
        Object beanName1 = SpringContextUtil.getBean(beanName);
        Method[] declaredMethods = beanName1.getClass().getDeclaredMethods();
        for (Method m : declaredMethods) {
            EventDrivenPattern annotation = m.getAnnotation(EventDrivenPattern.class);
            if (annotation != null) {
                beanMethodMaps.put(annotation.value(),
                        new BeanMethod(beanName1, m));
            }
        }
    }

    @Data
    @AllArgsConstructor
    public class BeanMethod {
        Object bean;
        Method method;
    }
}
  1. Detect all events, call different methods respectively, and call the driver
  2. The driver tag will call the corresponding method as long as it is a regular match, and multiple methods can be called
	@EventDrivenPattern("transCash.success")
    public void doSomething(String id) {
        CustomInfo customInfo = BaseRepository.find(id, CustomInfo.class);
        customInfo.handle1();
    }

Summary

In this way, we use the event driven of Spring to realize the event driven in the application of Qiqi architecture. The event driver provided by Spring by default is triggered synchronously and needs to be modified to be triggered asynchronously. So I implemented the asynchronous call annotation @ AsyncAnnotation.

@Aspect
public class AsyncAop {

    @Value("${ddd.async.timeout:1}")
    private int asyncTimeOut;

    @Pointcut("@annotation(com.github.well410.qiqiframework.dddcommon.infrastructure.utils.async.AsyncAnnotation)")
    public void asyncAspect() {
    }

    @Around("asyncAspect()")
    public Object async(ProceedingJoinPoint joinPoint) throws Exception {
        Future future = ((ThreadPoolTaskExecutor) SpringContextUtil.getBean("threadPoolTaskExecutor")).submit(new Callable() {
            @SneakyThrows
            @Override
            public Object call() throws Exception {
                Object o = null;
                try {
                    o = joinPoint.proceed();
                } catch (Exception t) {
                    t.printStackTrace();
                }
                return o;
            }
        });
        Object jsonResult = null;
        try {
            jsonResult = future.get(asyncTimeOut, TimeUnit.NANOSECONDS);
        } finally {
            return jsonResult;
        }

    }
}

This ensures that the main thread will not be affected during event driving.

Event driven entity result processing

In many cases, we need to persist events or do corresponding business logic processing according to the content of events. Qiqi architecture opens the processing of event entities.

  1. Let's first look at how Qiqi architecture defines event objects:
    DddEvent:
public class DddEvent<T extends EventSource> extends ApplicationEvent {
    public DddEvent(T t) {
        super(t);
    }

}

EventResult:

@Data
@Builder
public class EventResult {
    Class beanType;
    Method method;
    String eventType;
    boolean isSuccess;
    Object result;
    Exception exception;
}

EventSource:

@Data
public class EventSource {
    private long createTime;
    private long handleTime;
    private long finishTime;

    private String desc;
    private String eventType;

    private Object data;

    private List<EventResult> eventResults;

    public EventSource(String eventType) {
        this.createTime = System.currentTimeMillis();
        this.eventType = eventType;
    }

    public EventSource(String eventType, String desc) {
        this.createTime = System.currentTimeMillis();
        this.eventType = eventType;
        this.desc = desc;
    }

    public EventSource(String eventType, String desc, Object data) {
        this.createTime = System.currentTimeMillis();
        this.eventType = eventType;
        this.desc = desc;
        this.data = data;
    }

}

EventFactory:

public interface EventFactory {

    static DddEvent<EventSource> build(String eventType) {
        return new DddEvent<>(new EventSource(eventType));
    }

    static DddEvent<EventSource> build(String eventType, String desc) {
        return new DddEvent<>(new EventSource(eventType, desc));
    }

    static DddEvent<EventSource> build(String eventType, String desc, Object data) {
        return new DddEvent<>(new EventSource(eventType, desc, data));
    }
}

These are the definitions of event driven base objects.

  1. Define event result processing interface
public interface EventResultHandler {
    void handle(ApplicationEvent event);
}
  1. Scan the implementation class that implements the interface
@Data
@Slf4j
public class EventResultHandlerList {

    private List<EventResultHandler> eventResultHandlers = new ArrayList<>();

    public void registerMethods() {
        final String[] beanDefinitionNames = SpringContextUtil.getApplicationContext().getBeanDefinitionNames();
        for (String beanName : beanDefinitionNames) {
            Object beanName1 = SpringContextUtil.getBean(beanName);
            if (beanName1 == null) {
                continue;
            }
            if (EventResultHandler.class.isAssignableFrom(beanName1.getClass())) {
                registerHandlerMethod(beanName);
            }
        }
    }

    private void registerHandlerMethod(String beanName) {
        EventResultHandler bean = (EventResultHandler) SpringContextUtil.getBean(beanName);
        eventResultHandlers.add(bean);
    }
}
  1. Execute corresponding interception processing
  private void handleEventResult(ApplicationEvent event) {
        List<EventResultHandler> eventResultHandlers = eventResultHandlerList.getEventResultHandlers();
        for (EventResultHandler handler : eventResultHandlers) {
            try {
                handler.handle(event);
            } catch (Exception e) {
                log.error("When processing event results, the event handler<" + handler.getClass().getSimpleName() + ">Processing failed", e);
            }
        }
    }
  1. Define specific interceptors
    The following example prints the event object, and users can implement other processing logic by themselves
@Slf4j
public class PrintEventResultHandler implements EventResultHandler {
    @Override
    public void handle(ApplicationEvent event) {
        final EventSource eventSource = (EventSource) event.getSource();
        if (log.isDebugEnabled()) {
            log.info(eventSource.toString());
        }
    }
}

You only need to implement the EventResultHandler interface and inject it into the Spring container. When an event occurs, the Handler will be executed automatically. You can handle events accordingly.

Summary

With the function of intercepting event results, applications can do appropriate business processing for event objects. Such as warehousing, analysis and statistics, etc. In business development, appropriate event result processing processes can be developed.

Summary

The event driven module is finished. Qiqi architecture completes the business call of cross domain context required by DDD, which is the core concept of DDD and simplifies the development of DDD event driven.

Topics: Java kafka