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
- 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); }
- 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(); } }
- 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; } }
- Detect all events, call different methods respectively, and call the driver
- 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.
- 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.
- Define event result processing interface
public interface EventResultHandler { void handle(ApplicationEvent event); }
- 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); } }
- 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); } } }
- 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.