├── image └── 71895e721aced94523d752f63e5cb0c.png ├── cqrs-core ├── src │ └── main │ │ ├── java │ │ └── com │ │ │ └── damon │ │ │ └── cqrs │ │ │ ├── event │ │ │ ├── IEventSendingShceduler.java │ │ │ ├── ISendMessageService.java │ │ │ ├── EventSendingContext.java │ │ │ ├── IEventListener.java │ │ │ ├── DomainEventStream.java │ │ │ ├── EventCommittingContext.java │ │ │ └── AggregateEventAppendResult.java │ │ │ ├── snapshot │ │ │ └── IAggregateSnapshootService.java │ │ │ ├── cache │ │ │ ├── IAggregateCache.java │ │ │ └── DefaultAggregateCaffeineCache.java │ │ │ ├── store │ │ │ ├── IEventOffset.java │ │ │ ├── IEventStore.java │ │ │ └── IEventShardingRouting.java │ │ │ ├── utils │ │ │ ├── ThreadUtils.java │ │ │ ├── DateUtils.java │ │ │ ├── ReflectUtils.java │ │ │ ├── NamedThreadFactory.java │ │ │ ├── GenericsUtils.java │ │ │ └── ConsistentHashShard.java │ │ │ ├── exception │ │ │ ├── EventQueryException.java │ │ │ ├── EventOfffsetUpdateException.java │ │ │ └── EventSourcingException.java │ │ │ ├── CqrsApplicationContext.java │ │ │ ├── config │ │ │ ├── AggregateSlotLock.java │ │ │ └── CqrsConfig.java │ │ │ └── command │ │ │ └── ICommandService.java │ │ └── resources │ │ └── logback.xml └── pom.xml ├── cqrs-api ├── src │ └── main │ │ └── java │ │ └── com │ │ └── damon │ │ └── cqrs │ │ ├── domain │ │ ├── ValueObject.java │ │ ├── Entity.java │ │ ├── Event.java │ │ └── Command.java │ │ ├── exception │ │ ├── AggregateNotFoundException.java │ │ ├── EventStoreException.java │ │ ├── EventSendingException.java │ │ ├── AggregateEventConflictException.java │ │ ├── DuplicateEventStreamException.java │ │ ├── AggregateProcessingTimeoutException.java │ │ └── AggregateCommandConflictException.java │ │ └── utils │ │ └── AggregateConflictRetryUtils.java └── pom.xml ├── cqrs-sample ├── cqrs-sample-generic-test │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── damon │ │ │ │ │ └── cqrs │ │ │ │ │ └── sample │ │ │ │ │ ├── metting │ │ │ │ │ ├── api │ │ │ │ │ │ ├── MettingConstants.java │ │ │ │ │ │ ├── command │ │ │ │ │ │ │ ├── MettingGetCommand.java │ │ │ │ │ │ │ ├── MettingCreateCommand.java │ │ │ │ │ │ │ ├── MettingCancelCommand.java │ │ │ │ │ │ │ ├── MettingDTO.java │ │ │ │ │ │ │ └── MettingReserveCommand.java │ │ │ │ │ │ ├── event │ │ │ │ │ │ │ ├── MettingCreatedEvent.java │ │ │ │ │ │ │ ├── MettingCancelledEvent.java │ │ │ │ │ │ │ └── MettingReservedEvent.java │ │ │ │ │ │ └── IMettingCommandService.java │ │ │ │ │ ├── domain │ │ │ │ │ │ ├── aggregate │ │ │ │ │ │ │ ├── ReserveStatusEnum.java │ │ │ │ │ │ │ ├── CancelReservationStatusEnum.java │ │ │ │ │ │ │ ├── ReseveStatus.java │ │ │ │ │ │ │ ├── MeetingId.java │ │ │ │ │ │ │ ├── ReserveInfo.java │ │ │ │ │ │ │ └── MettingTime.java │ │ │ │ │ │ └── MettingCommandService.java │ │ │ │ │ ├── query │ │ │ │ │ │ └── MettingEventHandler.java │ │ │ │ │ └── MettingTest.java │ │ │ │ │ ├── train │ │ │ │ │ ├── aggregate │ │ │ │ │ │ └── value_object │ │ │ │ │ │ │ ├── enum_type │ │ │ │ │ │ │ ├── SEAT_PROTECT_TYPE.java │ │ │ │ │ │ │ ├── TICKET_CANCEL_STATUS.java │ │ │ │ │ │ │ ├── TICKET_BUY_STATUS.java │ │ │ │ │ │ │ ├── SEAT_TYPE.java │ │ │ │ │ │ │ ├── STATION_TICKET_LIMIT_STATUS.java │ │ │ │ │ │ │ ├── S2S_TICKET_PROTECT_CANCEL_STATUS.java │ │ │ │ │ │ │ └── S2S_TICKET_PROTECT_STATUS.java │ │ │ │ │ │ │ ├── SeatIndexSelected.java │ │ │ │ │ │ │ ├── TicketBuyStatus.java │ │ │ │ │ │ │ ├── UserSeatInfo.java │ │ │ │ │ │ │ ├── TrainCarriage.java │ │ │ │ │ │ │ └── S2SMaxTicketCountProtectInfo.java │ │ │ │ │ ├── command │ │ │ │ │ │ ├── TrainStockGetCommand.java │ │ │ │ │ │ ├── TicketCancelCommand.java │ │ │ │ │ │ ├── TicketProtectCancelCommand.java │ │ │ │ │ │ ├── TicketBuyCommand.java │ │ │ │ │ │ └── TicketProtectCommand.java │ │ │ │ │ ├── dto │ │ │ │ │ │ └── TrainStockDTO.java │ │ │ │ │ ├── listener │ │ │ │ │ │ └── TrainEventListener.java │ │ │ │ │ └── event │ │ │ │ │ │ ├── TicketProtectCanceledEvent.java │ │ │ │ │ │ ├── TicketCanceledEvent.java │ │ │ │ │ │ └── TicketProtectSucceedEvent.java │ │ │ │ │ ├── red_packet │ │ │ │ │ ├── api │ │ │ │ │ │ ├── command │ │ │ │ │ │ │ ├── RedPacketGetCommand.java │ │ │ │ │ │ │ ├── RedPacketGrabCommand.java │ │ │ │ │ │ │ └── RedPacketCreateCommand.java │ │ │ │ │ │ ├── dto │ │ │ │ │ │ │ └── WeixinRedPacketDTO.java │ │ │ │ │ │ ├── event │ │ │ │ │ │ │ ├── RedPacketGrabSucceedEvent.java │ │ │ │ │ │ │ └── RedPacketCreatedEvent.java │ │ │ │ │ │ └── IRedPacketCommandService.java │ │ │ │ │ ├── query │ │ │ │ │ │ └── event_handler │ │ │ │ │ │ │ └── RedPacketEventListener.java │ │ │ │ │ ├── domain │ │ │ │ │ │ └── service │ │ │ │ │ │ │ └── RedPacketCommandService.java │ │ │ │ │ ├── RedPacketServiceVirtualThreadBootstrap.java │ │ │ │ │ ├── RedPacketServiceBootstrap.java │ │ │ │ │ └── RedPacketConfig.java │ │ │ │ │ └── goods │ │ │ │ │ ├── api │ │ │ │ │ ├── GoodsStockCommitDeductionCommand.java │ │ │ │ │ ├── GoodsStockCancelDeductionCommand.java │ │ │ │ │ ├── GoodsStockCancelDeductedEvent.java │ │ │ │ │ ├── GoodsStockCommitDeductedEvent.java │ │ │ │ │ ├── GoodsStockTryDeductionCommand.java │ │ │ │ │ ├── GoodsCreateCommand.java │ │ │ │ │ ├── GoodsStockTryDeductedEvent.java │ │ │ │ │ └── GoodsCreatedEvent.java │ │ │ │ │ ├── query │ │ │ │ │ └── event_handler │ │ │ │ │ │ └── GoodsEventListener.java │ │ │ │ │ ├── domain │ │ │ │ │ ├── handler │ │ │ │ │ │ ├── IGoodsCommandService.java │ │ │ │ │ │ └── GoodsCommandService.java │ │ │ │ │ └── aggregate │ │ │ │ │ │ └── Goods.java │ │ │ │ │ ├── GoodsApplication.java │ │ │ │ │ └── GoodsVirtualThreadApplication.java │ │ │ └── resources │ │ │ │ └── logback.xml │ │ └── test │ │ │ └── java │ │ │ └── TestRun.java.bak │ └── README.md └── pom.xml ├── cqrs-event-mysql ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── damon │ │ │ └── cqrs │ │ │ └── event_store │ │ │ ├── DataSourceMapping.java │ │ │ ├── DefaultEventShardingRouting.java │ │ │ └── MysqlEventOffset.java │ └── test │ │ └── java │ │ └── com │ │ └── damon │ │ └── cqrs │ │ └── event_store │ │ └── Test.java └── pom.xml ├── .gitignore ├── cqrs-message-kafka ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── damon │ │ │ └── cqrs │ │ │ └── kafka │ │ │ ├── KafkaEventOrderlyListener.java │ │ │ ├── config │ │ │ ├── KafkaProducerConfig.java │ │ │ └── KafkaConsumerConfig.java │ │ │ ├── KafkaEventDispatch.java │ │ │ └── KafkaSendService.java │ └── test │ │ └── java │ │ └── KafkaProducerTest.java └── pom.xml ├── cqrs-spi ├── src │ ├── test │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── cqrs │ │ │ │ ├── com.damon.cqrs.spi.fixture.EmptySPI │ │ │ │ ├── com.damon.cqrs.spi.fixture.NoJoinSPI │ │ │ │ ├── com.damon.cqrs.spi.fixture.NotMatchSPI │ │ │ │ ├── com.damon.cqrs.spi.fixture.HasDefaultSPI │ │ │ │ ├── com.damon.cqrs.spi.fixture.NoClassMatchSPI │ │ │ │ ├── com.damon.cqrs.spi.fixture.ListSPI │ │ │ │ └── com.damon.cqrs.spi.fixture.JdbcSPI │ │ └── java │ │ │ └── com │ │ │ └── damon │ │ │ └── cqrs │ │ │ └── spi │ │ │ ├── fixture │ │ │ ├── NopSPI.java │ │ │ ├── SubNoJoinSPI.java │ │ │ ├── EmptySPI.java │ │ │ ├── NoJoinSPI.java │ │ │ ├── NotMatchSPI.java │ │ │ ├── NoClassMatchSPI.java │ │ │ ├── SubHasDefaultSPI.java │ │ │ ├── HasDefaultSPI.java │ │ │ ├── MysqlSPI.java │ │ │ ├── OracleSPI.java │ │ │ ├── ListSPI.java │ │ │ ├── JdbcSPI.java │ │ │ ├── CanNotInstantiatedSPI.java │ │ │ ├── ArrayListSPI.java │ │ │ └── LinkedListSPI.java │ │ │ └── SpiExtensionFactoryTest.java │ └── main │ │ └── java │ │ └── com │ │ └── damon │ │ └── cqrs │ │ └── spi │ │ ├── Join.java │ │ ├── ExtensionFactory.java │ │ ├── SpiExtensionFactory.java │ │ └── SPI.java └── pom.xml ├── cqrs-message-rocketmq ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── damon │ └── cqrs │ └── rocketmq │ ├── RocketMQSendService.java │ └── DefaultMQProducer.java ├── sql └── cqrs.sql └── README.md /image/71895e721aced94523d752f63e5cb0c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/654894017/cqrs/HEAD/image/71895e721aced94523d752f63e5cb0c.png -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/event/IEventSendingShceduler.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.event; 2 | 3 | public interface IEventSendingShceduler { 4 | 5 | void sendEvent(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /cqrs-api/src/main/java/com/damon/cqrs/domain/ValueObject.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.domain; 2 | 3 | 4 | import java.io.Serializable; 5 | 6 | public interface ValueObject extends Serializable { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/event/ISendMessageService.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.event; 2 | 3 | import java.util.List; 4 | 5 | public interface ISendMessageService { 6 | 7 | void sendMessage(List contexts); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/api/MettingConstants.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.api; 2 | 3 | public class MettingConstants { 4 | 5 | public final static Integer METTIING_TIME_SLOTS = 24 * 60; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/aggregate/value_object/enum_type/SEAT_PROTECT_TYPE.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.aggregate.value_object.enum_type; 2 | 3 | public enum SEAT_PROTECT_TYPE { 4 | GENERAL, STRICT_PROTECT; 5 | } -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/aggregate/value_object/enum_type/TICKET_CANCEL_STATUS.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.aggregate.value_object.enum_type; 2 | 3 | public enum TICKET_CANCEL_STATUS { 4 | SUCCEED, NOT_EXIST 5 | } -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/aggregate/value_object/enum_type/TICKET_BUY_STATUS.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.aggregate.value_object.enum_type; 2 | 3 | public enum TICKET_BUY_STATUS { 4 | SUCCEED, NOT_ENOUGH, BOUGHT; 5 | } -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/aggregate/value_object/enum_type/SEAT_TYPE.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.aggregate.value_object.enum_type; 2 | 3 | public enum SEAT_TYPE { 4 | BUSINESS_CLASS, FIRST_CLASS, SECOND_CLASS, STANDING; 5 | } 6 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/aggregate/value_object/enum_type/STATION_TICKET_LIMIT_STATUS.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.aggregate.value_object.enum_type; 2 | 3 | public enum STATION_TICKET_LIMIT_STATUS { 4 | SUCCEED, FAILED; 5 | } 6 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/aggregate/value_object/enum_type/S2S_TICKET_PROTECT_CANCEL_STATUS.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.aggregate.value_object.enum_type; 2 | 3 | public enum S2S_TICKET_PROTECT_CANCEL_STATUS { 4 | SUCCEED, NOT_EXIST; 5 | } -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/aggregate/value_object/enum_type/S2S_TICKET_PROTECT_STATUS.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.aggregate.value_object.enum_type; 2 | 3 | public enum S2S_TICKET_PROTECT_STATUS { 4 | SUCCEED, PROTECTED, NOT_ENOUGH; 5 | } -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/snapshot/IAggregateSnapshootService.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.snapshot; 2 | 3 | import com.damon.cqrs.domain.AggregateRoot; 4 | 5 | public interface IAggregateSnapshootService { 6 | 7 | void saveAggregateSnapshot(AggregateRoot aggregate); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/cache/IAggregateCache.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.cache; 2 | 3 | import com.damon.cqrs.domain.AggregateRoot; 4 | 5 | public interface IAggregateCache { 6 | 7 | void update(long id, AggregateRoot aggregate); 8 | 9 | T get(long id); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /cqrs-api/src/main/java/com/damon/cqrs/domain/Entity.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.domain; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 子类需要实现无参构造方法,不然bean复制时报错 7 | * 8 | * @author xianping_lu 9 | */ 10 | public interface Entity extends Serializable { 11 | Long getId(); 12 | 13 | void setId(Long id); 14 | 15 | } -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/store/IEventOffset.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.store; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public interface IEventOffset { 7 | 8 | void updateEventOffset(String dataSourceName, long offsetId, long id); 9 | 10 | List> queryEventOffset(); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/utils/ThreadUtils.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.utils; 2 | 3 | public class ThreadUtils { 4 | public static void sleep(long millis) { 5 | try { 6 | Thread.sleep(millis); 7 | } catch (InterruptedException e) { 8 | throw new RuntimeException(e); 9 | } 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/exception/EventQueryException.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.exception; 2 | 3 | public class EventQueryException extends RuntimeException { 4 | public EventQueryException(String message) { 5 | super(message); 6 | } 7 | 8 | public EventQueryException(Throwable cause) { 9 | super(cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/exception/EventOfffsetUpdateException.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.exception; 2 | 3 | public class EventOfffsetUpdateException extends RuntimeException { 4 | public EventOfffsetUpdateException(String message) { 5 | super(message); 6 | } 7 | 8 | public EventOfffsetUpdateException(Throwable cause) { 9 | super(cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/api/command/MettingGetCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.api.command; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | 5 | public class MettingGetCommand extends Command { 6 | 7 | public MettingGetCommand(Long commandId, Long aggregateId) { 8 | super(commandId, aggregateId); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /cqrs-event-mysql/src/main/java/com/damon/cqrs/event_store/DataSourceMapping.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.event_store; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | import javax.sql.DataSource; 7 | 8 | @Data 9 | @Builder 10 | public class DataSourceMapping { 11 | 12 | private String dataSourceName; 13 | 14 | private DataSource dataSource; 15 | 16 | private int tableNumber; 17 | } 18 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/event/EventSendingContext.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.event; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | import java.util.List; 8 | 9 | @Builder 10 | @Data 11 | public class EventSendingContext { 12 | private long offsetId; 13 | private List events; 14 | private Long aggregateId; 15 | private String aggregateType; 16 | } -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/utils/DateUtils.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.utils; 2 | 3 | import java.time.ZonedDateTime; 4 | 5 | public class DateUtils { 6 | 7 | public static long getSecond(ZonedDateTime first, ZonedDateTime second) { 8 | long secondMinutes = second.toInstant().getEpochSecond(); 9 | long firstMinutes = first.toInstant().getEpochSecond(); 10 | return secondMinutes - firstMinutes; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.classpath 2 | *.project 3 | .settings 4 | target 5 | *.springBeans 6 | bin 7 | .idea/cqrs.iml 8 | cqrs-core/.idea/compiler.xml 9 | cqrs-core/.idea/encodings.xml 10 | cqrs-core/.idea/jarRepositories.xml 11 | cqrs-core/.idea/misc.xml 12 | cqrs-core/.idea/workspace.xml 13 | /.idea/.gitignore 14 | /.idea/compiler.xml 15 | /.idea/encodings.xml 16 | /logs/error.log 17 | /.idea/jarRepositories.xml 18 | /logs/log.log 19 | /.idea/misc.xml 20 | /.idea/vcs.xml 21 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/command/TrainStockGetCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.command; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | 5 | public class TrainStockGetCommand extends Command { 6 | /** 7 | * @param commandId 8 | * @param aggregateId 9 | */ 10 | public TrainStockGetCommand(long commandId, long aggregateId) { 11 | super(commandId, aggregateId); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /cqrs-api/src/main/java/com/damon/cqrs/exception/AggregateNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.exception; 2 | 3 | import static java.lang.String.format; 4 | 5 | public class AggregateNotFoundException extends RuntimeException { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = 1621938828088924510L; 11 | 12 | public AggregateNotFoundException(long id) { 13 | super(format("Aggregate with id '%s' could not be found", id)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/exception/EventSourcingException.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.exception; 2 | 3 | public class EventSourcingException extends RuntimeException { 4 | public EventSourcingException(String message) { 5 | super(message); 6 | } 7 | 8 | public EventSourcingException(Throwable cause) { 9 | super(cause); 10 | } 11 | 12 | public EventSourcingException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/event/IEventListener.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.event; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 领域事件监听器 9 | * 10 | * @author xianpinglu 11 | */ 12 | public interface IEventListener { 13 | 14 | /** 15 | * 处理领域事件 16 | *

17 | * 注意:当前方法内部不需要try catch 否则会ack当前消息,当前事件已经按照分区进行分组,业务可以考虑批量处理。 18 | * 19 | * @param events 20 | */ 21 | void process(List> events); 22 | } 23 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/domain/aggregate/ReserveStatusEnum.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.domain.aggregate; 2 | 3 | public enum ReserveStatusEnum { 4 | 5 | SUCCEEDED(0, "预定成功"), 6 | BEOCCUPIED(-1, "区间已被占用"); 7 | private int status; 8 | 9 | private String message; 10 | 11 | ReserveStatusEnum(int status, String message) { 12 | this.status = status; 13 | this.message = message; 14 | } 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/event/DomainEventStream.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.event; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | @Data 11 | @Builder 12 | public class DomainEventStream { 13 | private Long aggregateId; 14 | private String aggregateType; 15 | private long commandId; 16 | private int version; 17 | private List events; 18 | private Map shardingParams; 19 | } 20 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/domain/aggregate/CancelReservationStatusEnum.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.domain.aggregate; 2 | 3 | public enum CancelReservationStatusEnum { 4 | 5 | SUCCEEDED(0, "预定成功"), 6 | NONEXISTENT(-1, "不存在预定信息"), 7 | UNMACHED(-2, "预定信息不匹配"); 8 | private int status; 9 | 10 | private String message; 11 | 12 | CancelReservationStatusEnum(int status, String message) { 13 | this.status = status; 14 | this.message = message; 15 | } 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/red_packet/api/command/RedPacketGetCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.red_packet.api.command; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | 5 | public class RedPacketGetCommand extends Command { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = 4010626387834949030L; 11 | 12 | /** 13 | * @param commandId 14 | * @param aggregateId 15 | */ 16 | public RedPacketGetCommand(long commandId, long aggregateId) { 17 | super(commandId, aggregateId); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/api/command/MettingCreateCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.api.command; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | 5 | public class MettingCreateCommand extends Command { 6 | 7 | private final String meetingDate; 8 | 9 | public MettingCreateCommand(Long commandId, Long meetingId, String meetingDate) { 10 | super(commandId, meetingId); 11 | this.meetingDate = meetingDate; 12 | } 13 | 14 | public String getMeetingDate() { 15 | return meetingDate; 16 | } 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/red_packet/api/dto/WeixinRedPacketDTO.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.red_packet.api.dto; 2 | 3 | import lombok.Data; 4 | 5 | import java.math.BigDecimal; 6 | import java.util.Map; 7 | import java.util.Stack; 8 | 9 | /** 10 | * @author xianpinglu 11 | */ 12 | @Data 13 | public class WeixinRedPacketDTO { 14 | /** 15 | * key 用户id value 抢到的金额 16 | */ 17 | private Map map; 18 | 19 | private Stack redpacketStack; 20 | 21 | private Long sponsorId; 22 | 23 | private Long id; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/red_packet/api/command/RedPacketGrabCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.red_packet.api.command; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | 5 | public class RedPacketGrabCommand extends Command { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = -3309773599641095159L; 11 | private Long userId; 12 | 13 | public RedPacketGrabCommand(Long commandId, Long redPacketId) { 14 | super(commandId, redPacketId); 15 | } 16 | 17 | public Long getUserId() { 18 | return userId; 19 | } 20 | 21 | public void setUserId(Long userId) { 22 | this.userId = userId; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cqrs-message-kafka/src/main/java/com/damon/cqrs/kafka/KafkaEventOrderlyListener.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.kafka; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | import com.damon.cqrs.event.IEventListener; 5 | import com.damon.cqrs.kafka.config.KafkaConsumerConfig; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * kafka消费者顺序消费 12 | */ 13 | @Slf4j 14 | public abstract class KafkaEventOrderlyListener implements IEventListener { 15 | public KafkaEventOrderlyListener(KafkaConsumerConfig config) { 16 | new KafkaEventDispatch(config.getProperties(), config.getTopic(), this::process).start(); 17 | } 18 | 19 | @Override 20 | public abstract void process(List> events); 21 | } -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/api/event/MettingCreatedEvent.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.api.event; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | 5 | 6 | public class MettingCreatedEvent extends Event { 7 | 8 | /** 9 | * 会议室日期 10 | */ 11 | private String meetingDate; 12 | 13 | public MettingCreatedEvent() { 14 | } 15 | 16 | public MettingCreatedEvent(String meetingDate) { 17 | this.meetingDate = meetingDate; 18 | } 19 | 20 | public String getMeetingDate() { 21 | return meetingDate; 22 | } 23 | 24 | public void setMeetingDate(String meetingDate) { 25 | this.meetingDate = meetingDate; 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/goods/api/GoodsStockCommitDeductionCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.goods.api; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | 5 | public class GoodsStockCommitDeductionCommand extends Command { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = 4371113646204443737L; 11 | private Long orderId; 12 | 13 | public GoodsStockCommitDeductionCommand(long commandId, long aggregateId) { 14 | super(commandId, aggregateId); 15 | } 16 | 17 | public Long getOrderId() { 18 | return orderId; 19 | } 20 | 21 | public void setOrderId(Long orderId) { 22 | this.orderId = orderId; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/goods/api/GoodsStockCancelDeductionCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.goods.api; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | 5 | public class GoodsStockCancelDeductionCommand extends Command { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = 4371113646204443737L; 11 | 12 | private Long orderId; 13 | 14 | public GoodsStockCancelDeductionCommand(long commandId, long aggregateId) { 15 | super(commandId, aggregateId); 16 | } 17 | 18 | public Long getOrderId() { 19 | return orderId; 20 | } 21 | 22 | public void setOrderId(Long orderId) { 23 | this.orderId = orderId; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/CqrsApplicationContext.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs; 2 | 3 | import com.damon.cqrs.command.ICommandService; 4 | import com.damon.cqrs.domain.AggregateRoot; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | @SuppressWarnings("unchecked") 10 | public class CqrsApplicationContext { 11 | 12 | private static Map> map = new HashMap<>(); 13 | 14 | public static synchronized void add(String aggregateType, ICommandService service) { 15 | map.put(aggregateType, service); 16 | } 17 | 18 | public static ICommandService get(String aggregateType) { 19 | return (ICommandService) map.get(aggregateType); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/api/command/MettingCancelCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.api.command; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | 5 | public class MettingCancelCommand extends Command { 6 | 7 | private String reserveFlag; 8 | 9 | private Long userId; 10 | 11 | public MettingCancelCommand(Long commandId, Long aggregateId, String reserveFlag, Long userId) { 12 | super(commandId, aggregateId); 13 | this.reserveFlag = reserveFlag; 14 | this.userId = userId; 15 | } 16 | 17 | public String getReserveFlag() { 18 | return reserveFlag; 19 | } 20 | 21 | 22 | public Long getUserId() { 23 | return userId; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/goods/api/GoodsStockCancelDeductedEvent.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.goods.api; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | 5 | public class GoodsStockCancelDeductedEvent extends Event { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = -7551341932379515484L; 11 | 12 | private Long orderId; 13 | 14 | public GoodsStockCancelDeductedEvent() { 15 | super(); 16 | } 17 | 18 | 19 | public GoodsStockCancelDeductedEvent(Long orderId) { 20 | this.orderId = orderId; 21 | } 22 | 23 | public Long getOrderId() { 24 | return orderId; 25 | } 26 | 27 | public void setOrderId(Long orderId) { 28 | this.orderId = orderId; 29 | } 30 | } -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/store/IEventStore.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.store; 2 | 3 | import com.damon.cqrs.domain.AggregateRoot; 4 | import com.damon.cqrs.domain.Event; 5 | import com.damon.cqrs.event.AggregateEventAppendResult; 6 | import com.damon.cqrs.event.DomainEventStream; 7 | import com.damon.cqrs.event.EventSendingContext; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | public interface IEventStore { 13 | 14 | AggregateEventAppendResult store(List streams); 15 | 16 | List> load(long aggregateId, Class aggregateClass, int startVersion, int endVersion, Map shardingParams); 17 | 18 | List queryWaitingSendEvents(String dataSourceName, String tableName, long offsetId); 19 | } 20 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/red_packet/api/event/RedPacketGrabSucceedEvent.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.red_packet.api.event; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | import lombok.Data; 5 | 6 | import java.math.BigDecimal; 7 | 8 | @Data 9 | public class RedPacketGrabSucceedEvent extends Event { 10 | 11 | /** 12 | * 13 | */ 14 | private static final long serialVersionUID = 8892155336754024236L; 15 | private BigDecimal money; 16 | private Long userId; 17 | private Long redPacketId; 18 | 19 | public RedPacketGrabSucceedEvent() { 20 | super(); 21 | } 22 | 23 | public RedPacketGrabSucceedEvent(BigDecimal money, Long userId) { 24 | this.money = money; 25 | this.userId = userId; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/domain/aggregate/ReseveStatus.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.domain.aggregate; 2 | 3 | public class ReseveStatus { 4 | private ReserveStatusEnum reserveStatusEnum; 5 | private String reserveFlag; 6 | 7 | public ReseveStatus(ReserveStatusEnum reserveStatusEnum, String reserveFlag) { 8 | this.reserveStatusEnum = reserveStatusEnum; 9 | this.reserveFlag = reserveFlag; 10 | } 11 | 12 | public ReseveStatus(ReserveStatusEnum reserveStatusEnum) { 13 | this.reserveStatusEnum = reserveStatusEnum; 14 | } 15 | 16 | public ReserveStatusEnum getReserveStatusEnum() { 17 | return reserveStatusEnum; 18 | } 19 | 20 | public String getReserveFlag() { 21 | return reserveFlag; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/goods/api/GoodsStockCommitDeductedEvent.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.goods.api; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | 5 | public class GoodsStockCommitDeductedEvent extends Event { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = -7551341932379515484L; 11 | 12 | private Long orderId; 13 | 14 | public GoodsStockCommitDeductedEvent() { 15 | super(); 16 | } 17 | 18 | /** 19 | * @param number 20 | */ 21 | public GoodsStockCommitDeductedEvent(Long orderId) { 22 | this.orderId = orderId; 23 | } 24 | 25 | public Long getOrderId() { 26 | return orderId; 27 | } 28 | 29 | public void setOrderId(Long orderId) { 30 | this.orderId = orderId; 31 | } 32 | } -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/event/EventCommittingContext.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.event; 2 | 3 | import com.damon.cqrs.domain.AggregateRoot; 4 | import com.damon.cqrs.domain.Event; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.concurrent.CompletableFuture; 11 | 12 | @Data 13 | @Builder 14 | public class EventCommittingContext { 15 | 16 | private long commandId; 17 | 18 | private List events; 19 | 20 | private CompletableFuture future; 21 | 22 | private Long aggregateId; 23 | 24 | private String aggregateTypeName; 25 | 26 | private int version; 27 | 28 | private EventCommittingMailBox mailBox; 29 | 30 | private AggregateRoot snapshot; 31 | 32 | private Map shardingParams; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/api/IMettingCommandService.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.api; 2 | 3 | import com.damon.cqrs.sample.metting.api.command.MettingCancelCommand; 4 | import com.damon.cqrs.sample.metting.api.command.MettingDTO; 5 | import com.damon.cqrs.sample.metting.api.command.MettingGetCommand; 6 | import com.damon.cqrs.sample.metting.api.command.MettingReserveCommand; 7 | import com.damon.cqrs.sample.metting.domain.aggregate.CancelReservationStatusEnum; 8 | import com.damon.cqrs.sample.metting.domain.aggregate.ReseveStatus; 9 | 10 | public interface IMettingCommandService { 11 | 12 | ReseveStatus reserve(MettingReserveCommand reserve); 13 | 14 | CancelReservationStatusEnum cancel(MettingCancelCommand cancel); 15 | 16 | MettingDTO get(MettingGetCommand get); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/resources/META-INF/cqrs/com.damon.cqrs.spi.fixture.EmptySPI: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. -------------------------------------------------------------------------------- /cqrs-event-mysql/src/test/java/com/damon/cqrs/event_store/Test.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.event_store; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.ExecutorService; 6 | import java.util.concurrent.Executors; 7 | 8 | public class Test { 9 | public static void main(String[] args) throws InterruptedException { 10 | ExecutorService service = Executors.newFixedThreadPool(5); 11 | new Thread(() -> { 12 | List list = new ArrayList(); 13 | System.out.println(1); 14 | for (int i = 0; i < 1000000; i++) { 15 | list.add(i); 16 | } 17 | try { 18 | Thread.sleep(10000000); 19 | } catch (InterruptedException e) { 20 | throw new RuntimeException(e); 21 | } 22 | }).start(); 23 | 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/goods/api/GoodsStockTryDeductionCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.goods.api; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | 5 | public class GoodsStockTryDeductionCommand extends Command { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = 4371113646204443737L; 11 | private int number; 12 | 13 | private Long orderId; 14 | 15 | public GoodsStockTryDeductionCommand(long commandId, long aggregateId) { 16 | super(commandId, aggregateId); 17 | } 18 | 19 | public int getNumber() { 20 | return number; 21 | } 22 | 23 | public void setNumber(int number) { 24 | this.number = number; 25 | } 26 | 27 | public Long getOrderId() { 28 | return orderId; 29 | } 30 | 31 | public void setOrderId(Long orderId) { 32 | this.orderId = orderId; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/config/AggregateSlotLock.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.config; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.concurrent.locks.ReentrantLock; 8 | 9 | /** 10 | * 聚合根锁 11 | */ 12 | @Data 13 | public class AggregateSlotLock { 14 | private final List locks = new ArrayList<>(); 15 | private final int lockNumber; 16 | 17 | public AggregateSlotLock(int lockNumber) { 18 | this.lockNumber = lockNumber; 19 | for (int i = 0; i < lockNumber; i++) { 20 | locks.add(new ReentrantLock(true)); 21 | } 22 | } 23 | 24 | public ReentrantLock getLock(Long aggregateId) { 25 | int hash = aggregateId.hashCode(); 26 | if (hash < 0) { 27 | hash = Math.abs(hash); 28 | } 29 | int index = hash % lockNumber; 30 | return locks.get(index); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/dto/TrainStockDTO.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.dto; 2 | 3 | import com.damon.cqrs.sample.train.aggregate.value_object.enum_type.SEAT_TYPE; 4 | 5 | import java.util.Map; 6 | import java.util.concurrent.ConcurrentSkipListMap; 7 | 8 | public class TrainStockDTO { 9 | 10 | private Map> s2sSeatCountMap; 11 | 12 | private Long id; 13 | 14 | public Map> getS2sSeatCountMap() { 15 | return s2sSeatCountMap; 16 | } 17 | 18 | public void setS2sSeatCountMap(Map> s2sSeatCountMap) { 19 | this.s2sSeatCountMap = s2sSeatCountMap; 20 | } 21 | 22 | public Long getId() { 23 | return id; 24 | } 25 | 26 | public void setId(Long id) { 27 | this.id = id; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/red_packet/api/event/RedPacketCreatedEvent.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.red_packet.api.event; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | import lombok.Data; 5 | 6 | import java.math.BigDecimal; 7 | import java.util.Stack; 8 | 9 | @Data 10 | public class RedPacketCreatedEvent extends Event { 11 | 12 | /** 13 | * 14 | */ 15 | private static final long serialVersionUID = 2969013356235525800L; 16 | 17 | private Stack redpacketStack; 18 | 19 | private Long sponsorId; 20 | 21 | private BigDecimal money; 22 | 23 | private BigDecimal minMoney; 24 | 25 | private BigDecimal number; 26 | 27 | public RedPacketCreatedEvent() { 28 | super(); 29 | } 30 | 31 | public RedPacketCreatedEvent(Stack redpacketStack) { 32 | super(); 33 | this.redpacketStack = redpacketStack; 34 | } 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/resources/META-INF/cqrs/com.damon.cqrs.spi.fixture.NoJoinSPI: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | subNoJoinSPI=com.damon.cqrs.spi.fixture.SubNoJoinSPI 18 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/resources/META-INF/cqrs/com.damon.cqrs.spi.fixture.NotMatchSPI: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | subNoJoinSPI=com.damon.cqrs.spi.fixture.SubNoJoinSPI 18 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/api/command/MettingDTO.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.api.command; 2 | 3 | import com.damon.cqrs.sample.metting.domain.aggregate.ReserveInfo; 4 | import lombok.Data; 5 | import lombok.ToString; 6 | 7 | import java.io.Serializable; 8 | import java.util.BitSet; 9 | import java.util.Map; 10 | 11 | @Data 12 | @ToString 13 | public class MettingDTO implements Serializable { 14 | 15 | private BitSet schedule; 16 | /** 17 | * 会议室日期 18 | */ 19 | private String meetingDate; 20 | /** 21 | * 会议室预定记录 22 | */ 23 | private Map reserveRecord; 24 | 25 | public MettingDTO(BitSet schedule, String meetingDate, Map reserveRecord) { 26 | this.schedule = schedule; 27 | this.meetingDate = meetingDate; 28 | this.reserveRecord = reserveRecord; 29 | } 30 | 31 | public MettingDTO() { 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/resources/META-INF/cqrs/com.damon.cqrs.spi.fixture.HasDefaultSPI: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | subHasDefaultSPI=com.damon.cqrs.spi.fixture.SubHasDefaultSPI 18 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/resources/META-INF/cqrs/com.damon.cqrs.spi.fixture.NoClassMatchSPI: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | subNoClassMatchSPI=com.damon.cqrs.spi.fixture.SubNoClassMatchSPI 18 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/goods/api/GoodsCreateCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.goods.api; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | 5 | public class GoodsCreateCommand extends Command { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = -452309907057579164L; 11 | private String name; 12 | private int number; 13 | 14 | public GoodsCreateCommand(long commandId, long aggregateId, String name, int number) { 15 | super(commandId, aggregateId); 16 | this.name = name; 17 | this.number = number; 18 | 19 | } 20 | 21 | public String getName() { 22 | return name; 23 | } 24 | 25 | public void setName(String name) { 26 | this.name = name; 27 | } 28 | 29 | public int getNumber() { 30 | return number; 31 | } 32 | 33 | public void setNumber(int number) { 34 | this.number = number; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/api/event/MettingCancelledEvent.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.api.event; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class MettingCancelledEvent extends Event { 8 | 9 | private String reserveFlag; 10 | private int start; 11 | private int end; 12 | 13 | public MettingCancelledEvent() { 14 | 15 | } 16 | 17 | public MettingCancelledEvent(String reserveFlag, int start, int end) { 18 | this.reserveFlag = reserveFlag; 19 | this.start = start; 20 | this.end = end; 21 | } 22 | 23 | public int getStart() { 24 | return start; 25 | } 26 | 27 | public void setStart(int start) { 28 | this.start = start; 29 | } 30 | 31 | public int getEnd() { 32 | return end; 33 | } 34 | 35 | public void setEnd(int end) { 36 | this.end = end; 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/goods/query/event_handler/GoodsEventListener.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.goods.query.event_handler; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.damon.cqrs.domain.Event; 5 | import com.damon.cqrs.kafka.KafkaEventOrderlyListener; 6 | import com.damon.cqrs.kafka.config.KafkaConsumerConfig; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * goods事件监听器 13 | * 14 | * @author xianpinglu 15 | */ 16 | @Slf4j 17 | public class GoodsEventListener extends KafkaEventOrderlyListener { 18 | 19 | public GoodsEventListener(KafkaConsumerConfig consumerConfig) { 20 | super(consumerConfig); 21 | } 22 | 23 | @Override 24 | public void process(List> events) { 25 | events.forEach(eventList -> { 26 | System.out.println(Thread.currentThread().getName() + ":" + JSONObject.toJSONString(eventList)); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/domain/aggregate/MeetingId.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.domain.aggregate; 2 | 3 | import lombok.Data; 4 | import lombok.NonNull; 5 | 6 | @Data 7 | public class MeetingId { 8 | private final String meetingDate; 9 | private final String meettingNumber; 10 | 11 | public MeetingId(@NonNull String meetingDate, @NonNull String meettingNumber) { 12 | this.meetingDate = meetingDate; 13 | this.meettingNumber = meettingNumber; 14 | } 15 | 16 | public MeetingId(@NonNull Long meetingId) { 17 | if (meetingId == null) { 18 | throw new IllegalArgumentException("mettingId is null"); 19 | } 20 | this.meetingDate = meetingId.toString().substring(0, 8); 21 | this.meettingNumber = meetingId.toString().substring(8); 22 | } 23 | 24 | public Long getId() { 25 | return Long.parseLong(meetingDate + meettingNumber); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/red_packet/api/IRedPacketCommandService.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.red_packet.api; 2 | 3 | import com.damon.cqrs.sample.red_packet.api.command.RedPacketCreateCommand; 4 | import com.damon.cqrs.sample.red_packet.api.command.RedPacketGetCommand; 5 | import com.damon.cqrs.sample.red_packet.api.command.RedPacketGrabCommand; 6 | import com.damon.cqrs.sample.red_packet.api.dto.WeixinRedPacketDTO; 7 | 8 | public interface IRedPacketCommandService { 9 | /** 10 | * 创建红包 11 | * 12 | * @param command 13 | */ 14 | void createRedPackage(RedPacketCreateCommand command); 15 | 16 | /** 17 | * 抢红包 18 | * 19 | * @param command 20 | * @return 21 | */ 22 | int grabRedPackage(RedPacketGrabCommand command); 23 | 24 | /** 25 | * 红包详情 26 | * 27 | * @param command 28 | * @return 29 | */ 30 | WeixinRedPacketDTO get(RedPacketGetCommand command); 31 | } 32 | 33 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/fixture/NopSPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi.fixture; 19 | 20 | public interface NopSPI { 21 | } 22 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/store/IEventShardingRouting.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.store; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * 聚合路由 7 | *

8 | * 1个实例对应n个分片 9 | * 10 | * @author xianpinglu 11 | */ 12 | public interface IEventShardingRouting { 13 | /** 14 | * 路由到实例 15 | * 16 | * @param aggregateId 聚合根id 17 | * @param aggregateType 聚合根列席 18 | * @param instanceNumber 实例数量 19 | * @param shardingParams 自定义分片参数 20 | * @return 21 | */ 22 | Integer routeInstance(Long aggregateId, String aggregateType, Integer instanceNumber, Map shardingParams); 23 | 24 | /** 25 | * 路由到分片 26 | * 27 | * @param aggregateId 聚合根id 28 | * @param aggregateType 聚合根类型 29 | * @param shardingNumber 分片数量 30 | * @param shardingParams 自定义分片参数 31 | * @return 32 | */ 33 | Integer routeSharding(Long aggregateId, String aggregateType, Integer shardingNumber, Map shardingParams); 34 | } 35 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/utils/ReflectUtils.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.utils; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | 5 | public class ReflectUtils { 6 | 7 | @SuppressWarnings("unchecked") 8 | public static T newInstance(Class classes, Long aggregateId) { 9 | try { 10 | return (T) classes.getDeclaredConstructor(Long.class).newInstance(aggregateId); 11 | } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | 12 | InvocationTargetException | NoSuchMethodException | SecurityException e) { 13 | throw new RuntimeException(e); 14 | } 15 | 16 | } 17 | 18 | @SuppressWarnings("unchecked") 19 | public static Class getClass(String classTypeName) { 20 | try { 21 | return (Class) Class.forName(classTypeName); 22 | } catch (ClassNotFoundException e) { 23 | throw new RuntimeException(e); 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/resources/META-INF/cqrs/com.damon.cqrs.spi.fixture.ListSPI: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | arrayList=com.damon.cqrs.spi.fixture.ArrayListSPI 18 | linkedList=com.damon.cqrs.spi.fixture.LinkedListSPI 19 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/goods/domain/handler/IGoodsCommandService.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.goods.domain.handler; 2 | 3 | 4 | import com.damon.cqrs.sample.goods.api.GoodsCreateCommand; 5 | import com.damon.cqrs.sample.goods.api.GoodsStockCancelDeductionCommand; 6 | import com.damon.cqrs.sample.goods.api.GoodsStockCommitDeductionCommand; 7 | import com.damon.cqrs.sample.goods.api.GoodsStockTryDeductionCommand; 8 | import com.damon.cqrs.sample.goods.domain.aggregate.Goods; 9 | 10 | import java.util.concurrent.CompletableFuture; 11 | 12 | public interface IGoodsCommandService { 13 | 14 | CompletableFuture createGoodsStock(GoodsCreateCommand command); 15 | 16 | CompletableFuture tryDeductionStock(GoodsStockTryDeductionCommand command); 17 | 18 | CompletableFuture commitDeductionStock(GoodsStockCommitDeductionCommand command); 19 | 20 | CompletableFuture cancelDeductionStock(GoodsStockCancelDeductionCommand command); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/aggregate/value_object/SeatIndexSelected.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.aggregate.value_object; 2 | 3 | import com.damon.cqrs.sample.train.aggregate.value_object.enum_type.SEAT_PROTECT_TYPE; 4 | 5 | import java.util.Map; 6 | 7 | public class SeatIndexSelected { 8 | 9 | private Map seatIndexs; 10 | 11 | private Integer weight; 12 | 13 | public SeatIndexSelected(Map seatIndexs, Integer weight) { 14 | this.seatIndexs = seatIndexs; 15 | this.weight = weight; 16 | } 17 | 18 | public Map getSeatIndexs() { 19 | return seatIndexs; 20 | } 21 | 22 | public void setSeatIndexs(Map seatIndexs) { 23 | this.seatIndexs = seatIndexs; 24 | } 25 | 26 | public Integer getWeight() { 27 | return weight; 28 | } 29 | 30 | public void setWeight(Integer weight) { 31 | this.weight = weight; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/resources/META-INF/cqrs/com.damon.cqrs.spi.fixture.JdbcSPI: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | mysql=com.damon.cqrs.spi.fixture.MysqlSPI 18 | oracle=com.damon.cqrs.spi.fixture.OracleSPI 19 | canNotInstantiated=com.damon.cqrs.spi.fixture.CanNotInstantiatedSPI 20 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/fixture/SubNoJoinSPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi.fixture; 19 | 20 | /** 21 | * no join SPI class. 22 | */ 23 | public class SubNoJoinSPI implements NoJoinSPI { 24 | 25 | } 26 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/goods/api/GoodsStockTryDeductedEvent.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.goods.api; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | 5 | public class GoodsStockTryDeductedEvent extends Event { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = -7551341932379515484L; 11 | 12 | private Long orderId; 13 | private int number; 14 | 15 | public GoodsStockTryDeductedEvent() { 16 | super(); 17 | } 18 | 19 | 20 | /** 21 | * @param number 22 | */ 23 | public GoodsStockTryDeductedEvent(Long orderId, int number) { 24 | this.number = number; 25 | this.orderId = orderId; 26 | } 27 | 28 | public int getNumber() { 29 | return number; 30 | } 31 | 32 | public void setNumber(int number) { 33 | this.number = number; 34 | } 35 | 36 | public Long getOrderId() { 37 | return orderId; 38 | } 39 | 40 | public void setOrderId(Long orderId) { 41 | this.orderId = orderId; 42 | } 43 | } -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/listener/TrainEventListener.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.listener; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.damon.cqrs.domain.Event; 5 | import com.damon.cqrs.rocketmq.RocketMQOrderlyEventListener; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.rocketmq.client.exception.MQClientException; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * Q端车次事件监听器,更新Q端数据 13 | * 14 | * @author xianpinglu 15 | */ 16 | @Slf4j 17 | public class TrainEventListener extends RocketMQOrderlyEventListener { 18 | 19 | public TrainEventListener(String nameServer, String topic, String consumerGroup, int minThread, int maxThread, int pullBatchSize) throws MQClientException { 20 | super(nameServer, topic, consumerGroup, minThread, maxThread, pullBatchSize); 21 | } 22 | 23 | @Override 24 | public void process(List> events) { 25 | 26 | events.forEach(event -> { 27 | log.info(JSONObject.toJSONString(event)); 28 | }); 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/fixture/EmptySPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi.fixture; 19 | 20 | import com.damon.cqrs.spi.SPI; 21 | 22 | /** 23 | * EmptySPI . 24 | */ 25 | @SPI 26 | public interface EmptySPI { 27 | 28 | } 29 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/fixture/NoJoinSPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi.fixture; 19 | 20 | import com.damon.cqrs.spi.SPI; 21 | 22 | /** 23 | * no join test SPI interface. 24 | */ 25 | @SPI 26 | public interface NoJoinSPI { 27 | 28 | } 29 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/query/MettingEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.query; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.damon.cqrs.domain.Event; 5 | import com.damon.cqrs.rocketmq.RocketMQOrderlyEventListener; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.rocketmq.client.exception.MQClientException; 8 | import org.apache.rocketmq.common.consumer.ConsumeFromWhere; 9 | 10 | import java.util.List; 11 | 12 | @Slf4j 13 | public class MettingEventHandler extends RocketMQOrderlyEventListener { 14 | 15 | public MettingEventHandler(String nameServer, String topic, String consumerGroup, int minThread, int maxThread, int pullBatchSize, ConsumeFromWhere where) throws MQClientException { 16 | super(nameServer, topic, consumerGroup, minThread, maxThread, pullBatchSize, where); 17 | } 18 | 19 | @Override 20 | public void process(List> events) { 21 | 22 | events.forEach(event -> { 23 | log.info(JSONObject.toJSONString(event)); 24 | }); 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/fixture/NotMatchSPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi.fixture; 19 | 20 | import com.damon.cqrs.spi.SPI; 21 | 22 | /** 23 | * not match test SPI interface. 24 | */ 25 | @SPI 26 | public interface NotMatchSPI { 27 | 28 | } 29 | -------------------------------------------------------------------------------- /cqrs-event-mysql/src/main/java/com/damon/cqrs/event_store/DefaultEventShardingRouting.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.event_store; 2 | 3 | import com.damon.cqrs.store.IEventShardingRouting; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * 聚合路由 9 | *

10 | * 根据聚合根id选择相应的数据源,数据表 11 | * 12 | * @author xianpinglu 13 | */ 14 | public class DefaultEventShardingRouting implements IEventShardingRouting { 15 | 16 | @Override 17 | public Integer routeInstance(Long aggregateId, String aggregateType, Integer instanceNumber, Map shardingParams) { 18 | int hash = aggregateId.hashCode(); 19 | hash = hash < 0 ? Math.abs(hash) : hash; 20 | return hash % instanceNumber; 21 | } 22 | 23 | @Override 24 | public Integer routeSharding(Long aggregateId, String aggregateType, Integer shardingNumber, Map shardingParams) { 25 | // Long aggreId = aggregateId / 100; 26 | // int hash = aggreId.hashCode(); 27 | int hash = aggregateId.hashCode(); 28 | hash = hash < 0 ? Math.abs(hash) : hash; 29 | return hash % shardingNumber; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/red_packet/query/event_handler/RedPacketEventListener.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.red_packet.query.event_handler; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.damon.cqrs.domain.Event; 5 | import com.damon.cqrs.rocketmq.RocketMQOrderlyEventListener; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.rocketmq.client.exception.MQClientException; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * 红包事件监听器 13 | * 14 | * @author xianpinglu 15 | */ 16 | @Slf4j 17 | public class RedPacketEventListener extends RocketMQOrderlyEventListener { 18 | 19 | public RedPacketEventListener(String nameServer, String topic, String consumerGroup, int minThread, int maxThread, int pullBatchSize) throws MQClientException { 20 | super(nameServer, topic, consumerGroup, minThread, maxThread, pullBatchSize); 21 | } 22 | 23 | @Override 24 | public void process(List> events) { 25 | 26 | events.forEach(event -> { 27 | log.info(JSONObject.toJSONString(event)); 28 | }); 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/fixture/NoClassMatchSPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi.fixture; 19 | 20 | import com.damon.cqrs.spi.SPI; 21 | 22 | /** 23 | * no class match test SPI interface. 24 | */ 25 | @SPI 26 | public interface NoClassMatchSPI { 27 | 28 | } 29 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/domain/aggregate/ReserveInfo.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.domain.aggregate; 2 | 3 | import com.damon.cqrs.domain.ValueObject; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | public class ReserveInfo implements ValueObject { 8 | 9 | private Long userId; 10 | 11 | private int start; 12 | 13 | private int end; 14 | 15 | private String mettingTopic; 16 | 17 | private String mettingContent; 18 | 19 | private String attachmentUrl; 20 | 21 | public ReserveInfo() { 22 | 23 | } 24 | 25 | public ReserveInfo(Long userId, 26 | int start, 27 | int end, 28 | String mettingTopic, 29 | String mettingContent, 30 | String attachmentUrl 31 | ) { 32 | this.userId = userId; 33 | this.start = start; 34 | this.end = end; 35 | this.mettingTopic = mettingTopic; 36 | this.mettingContent = mettingContent; 37 | this.attachmentUrl = attachmentUrl; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /cqrs-message-kafka/src/main/java/com/damon/cqrs/kafka/config/KafkaProducerConfig.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.kafka.config; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Properties; 6 | 7 | @Data 8 | public class KafkaProducerConfig { 9 | private String topic; 10 | private String bootstrapServers; 11 | private Properties properties; 12 | 13 | public KafkaProducerConfig(String bootstrapServers, String topic) { 14 | Properties properties = new Properties(); 15 | properties.put("bootstrap.servers", bootstrapServers); 16 | properties.put("acks", "all"); 17 | properties.put("retries", 0); 18 | properties.put("batch.size", 16384); 19 | properties.put("linger.ms", 20); 20 | properties.put("buffer.memory", 33554432); 21 | properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); 22 | properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); 23 | this.properties = properties; 24 | this.bootstrapServers = bootstrapServers; 25 | this.topic = topic; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/fixture/SubHasDefaultSPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi.fixture; 19 | 20 | import com.damon.cqrs.spi.Join; 21 | 22 | /** 23 | * default spi class. 24 | */ 25 | @Join 26 | public class SubHasDefaultSPI implements HasDefaultSPI { 27 | 28 | } 29 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/fixture/HasDefaultSPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi.fixture; 19 | 20 | import com.damon.cqrs.spi.SPI; 21 | 22 | /** 23 | * has default value test SPI interface. 24 | */ 25 | @SPI("subHasDefaultSPI") 26 | public interface HasDefaultSPI { 27 | 28 | } 29 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/domain/aggregate/MettingTime.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.domain.aggregate; 2 | 3 | import com.damon.cqrs.domain.ValueObject; 4 | import com.damon.cqrs.sample.metting.api.MettingConstants; 5 | import lombok.NonNull; 6 | 7 | 8 | public class MettingTime implements ValueObject { 9 | private Integer start; 10 | private Integer end; 11 | 12 | public MettingTime(@NonNull Integer start, @NonNull Integer end) { 13 | if (start >= end) { 14 | throw new IllegalArgumentException("会议时间范围无效"); 15 | } 16 | if (start < 0 || start > MettingConstants.METTIING_TIME_SLOTS) { 17 | throw new IllegalArgumentException("会议开始时间无效"); 18 | } 19 | if (end < 1 || end > MettingConstants.METTIING_TIME_SLOTS) { 20 | throw new IllegalArgumentException("会议结束时间无效"); 21 | } 22 | this.start = start; 23 | this.end = end; 24 | } 25 | 26 | public Integer getStart() { 27 | return start; 28 | } 29 | 30 | public Integer getEnd() { 31 | return end; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/api/event/MettingReservedEvent.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.api.event; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class MettingReservedEvent extends Event { 8 | 9 | private int start; 10 | private int end; 11 | private Long userId; 12 | private String reserveFlag; 13 | private String mettingTopic; 14 | private String mettingContent; 15 | private String attachmentUrl; 16 | 17 | public MettingReservedEvent(int start, int end, Long userId, String reserveFlag, 18 | String mettingTopic, 19 | String mettingContent, 20 | String attachmentUrl) { 21 | this.start = start; 22 | this.end = end; 23 | this.userId = userId; 24 | this.reserveFlag = reserveFlag; 25 | this.mettingTopic = mettingTopic; 26 | this.mettingContent = mettingContent; 27 | this.attachmentUrl = attachmentUrl; 28 | } 29 | 30 | public MettingReservedEvent() { 31 | 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/fixture/MysqlSPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi.fixture; 19 | 20 | import com.damon.cqrs.spi.Join; 21 | 22 | @Join 23 | public class MysqlSPI implements JdbcSPI { 24 | 25 | @Override 26 | public String getClassName() { 27 | return "mysql"; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/fixture/OracleSPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi.fixture; 19 | 20 | import com.damon.cqrs.spi.Join; 21 | 22 | @Join 23 | public class OracleSPI implements JdbcSPI { 24 | 25 | @Override 26 | public String getClassName() { 27 | return "oracle"; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/README.md: -------------------------------------------------------------------------------- 1 | ### 12306购票 2 | 3 | 12306主要性能问题还是出在出票和余票查询。当前示例通过CQRS架构结合聚合根在内存、事件回溯的手段来解决以上两个问题。 4 | 实现了买票、退票、站点最多可卖座位数限制、 站点间预留票(最低保留座位数,最多可卖座位数)、取消预留票功能。 5 | 选择座位ABC-DE暂未实现、增加、减少车厢暂未实现。现有的座位没有归属车厢的概念,如果要支持只需要做好座位号码段的隐射即可不会太复杂。 6 | 当前示例并不是要去实现一个12306的买票功能,12306的逻辑还会比这复杂很多。只是为了证明通过聚合根在内存、事件回溯的设计模式可以很好的处理非常复杂的业务领域问题, 7 | 同时又能够做到高性能。后话意淫一下:我觉得12306也采取这样的模式去设计库存这块,我想会颠覆他们现有的一个库存处理的架构体系,完全有别于现有的另一套架构体系风格。 8 | 带来更好的性能,简化整个业务架构,去掉Gemfire也不是不可能。 9 | 10 | ### Command端 11 | 12 | 抽象车次为聚合根。
13 | 假设有某个列车K41车次有6个站点分别为1,2,3,4,5,6,总共100个座位
14 | 我们可以按照一下方式进行座位划分:
15 | 10002-100 表示站点1到站点2的票为100。
16 | 20003-100 表示站点2到站点3的票为100。
17 | 30004-100 表示站点3到站点4的票为100。
18 | 40005-100 表示站点4到站点5的票为100。
19 | 50006-100 表示站点5到站点6的票为100。
20 | 如果用户需要购买站点1到站点3的票,首先我们需要判断1到2(10002),2到3(20003)站点的余票是否大于0, 21 | 如果大于0说明可以购票,然后分别扣减10002,10003分别减1。如果用户需要取消站点1到站点3的购买的票,只需要分别对10002,10003分别加1即可。 22 | 23 | 场景1:当卖掉了一张A到F的车票,则AF之间所有站站库存要减1; 场景2:列车中途需要增加减少车厢,或者有一节车厢不对外出售; 场景3: 24 | 为了降低空座率,需要特定站站最多卖票数量,特定出发站的最多卖票数量; 场景4: 25 | 由于座位的唯一性,需要标示特定座位某站站已被卖出,并且找到满足特定条件的座位; 场景5:某特定车厢或座位只能卖给特定始发站的人群,即某特定车厢是给某个车站预留的; 26 | 27 | ### Query端 28 | 29 | 监听EventListener,消费座位数量变更事件信息更新Q端数据库。 30 | 31 | 32 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/goods/api/GoodsCreatedEvent.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.goods.api; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | 5 | public class GoodsCreatedEvent extends Event { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = 5797757720163756860L; 11 | 12 | private String name; 13 | 14 | private long id; 15 | 16 | private int number; 17 | 18 | public GoodsCreatedEvent() { 19 | super(); 20 | } 21 | 22 | public GoodsCreatedEvent(long id, String name, int number) { 23 | this.id = id; 24 | this.number = number; 25 | this.name = name; 26 | } 27 | 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public void setName(String name) { 33 | this.name = name; 34 | } 35 | 36 | public long getId() { 37 | return id; 38 | } 39 | 40 | public void setId(long id) { 41 | this.id = id; 42 | } 43 | 44 | public int getNumber() { 45 | return number; 46 | } 47 | 48 | public void setNumber(int number) { 49 | this.number = number; 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /cqrs-spi/src/main/java/com/damon/cqrs/spi/Join.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi; 19 | 20 | import java.lang.annotation.*; 21 | 22 | /** 23 | * Join 24 | * Adding this annotation to a class indicates joining the extension mechanism. 25 | */ 26 | @Documented 27 | @Retention(RetentionPolicy.RUNTIME) 28 | @Target(ElementType.TYPE) 29 | public @interface Join { 30 | } 31 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/event/TicketProtectCanceledEvent.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.event; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | import com.damon.cqrs.sample.train.aggregate.value_object.enum_type.SEAT_TYPE; 5 | 6 | public class TicketProtectCanceledEvent extends Event { 7 | private Integer startStationNumber; 8 | private Integer endStationNumber; 9 | 10 | private SEAT_TYPE seatType; 11 | 12 | public TicketProtectCanceledEvent() { 13 | } 14 | 15 | public Integer getStartStationNumber() { 16 | return startStationNumber; 17 | } 18 | 19 | public void setStartStationNumber(Integer startStationNumber) { 20 | this.startStationNumber = startStationNumber; 21 | } 22 | 23 | public Integer getEndStationNumber() { 24 | return endStationNumber; 25 | } 26 | 27 | public void setEndStationNumber(Integer endStationNumber) { 28 | this.endStationNumber = endStationNumber; 29 | } 30 | 31 | public SEAT_TYPE getSeatType() { 32 | return seatType; 33 | } 34 | 35 | public void setSeatType(SEAT_TYPE seatType) { 36 | this.seatType = seatType; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/fixture/ListSPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi.fixture; 19 | 20 | import com.damon.cqrs.spi.SPI; 21 | 22 | import java.util.List; 23 | 24 | /** 25 | * ListSpi . 26 | */ 27 | @SPI 28 | public interface ListSPI { 29 | 30 | /** 31 | * Gets list. 32 | * 33 | * @return the list 34 | */ 35 | List getList(); 36 | } 37 | -------------------------------------------------------------------------------- /cqrs-message-rocketmq/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 4.0.0 7 | 8 | com.damon.cqrs 9 | cqrs 10 | ${revision} 11 | 12 | cqrs-message-rocketmq 13 | cqrs-message-rocketmq 14 | 15 | UTF-8 16 | 17 | 18 | 19 | com.damon.cqrs 20 | cqrs-api 21 | 22 | 23 | com.damon.cqrs 24 | cqrs-core 25 | 26 | 27 | org.apache.rocketmq 28 | rocketmq-client 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/command/TicketCancelCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.command; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | 5 | public class TicketCancelCommand extends Command { 6 | private Long userId; 7 | private Integer startStationNumber; 8 | private Integer endStationNumber; 9 | 10 | /** 11 | * @param commandId 12 | * @param aggregateId 13 | */ 14 | public TicketCancelCommand(long commandId, long aggregateId) { 15 | super(commandId, aggregateId); 16 | } 17 | 18 | public Long getUserId() { 19 | return userId; 20 | } 21 | 22 | public void setUserId(Long userId) { 23 | this.userId = userId; 24 | } 25 | 26 | public Integer getStartStationNumber() { 27 | return startStationNumber; 28 | } 29 | 30 | public void setStartStationNumber(Integer startStationNumber) { 31 | this.startStationNumber = startStationNumber; 32 | } 33 | 34 | public Integer getEndStationNumber() { 35 | return endStationNumber; 36 | } 37 | 38 | public void setEndStationNumber(Integer endStationNumber) { 39 | this.endStationNumber = endStationNumber; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/fixture/JdbcSPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi.fixture; 19 | 20 | import com.damon.cqrs.spi.SPI; 21 | 22 | /** 23 | * The interface Jdbc spi. 24 | */ 25 | @SPI 26 | public interface JdbcSPI { 27 | 28 | /** 29 | * Gets class name. 30 | * 31 | * @return the class name 32 | */ 33 | @SuppressWarnings("unused") 34 | String getClassName(); 35 | } 36 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/utils/NamedThreadFactory.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.utils; 2 | 3 | import java.util.concurrent.ThreadFactory; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | public class NamedThreadFactory implements ThreadFactory { 7 | 8 | public final String namePrefix; 9 | private final AtomicInteger poolNumber = new AtomicInteger(1); 10 | private final ThreadGroup threadGroup; 11 | private final AtomicInteger threadNumber = new AtomicInteger(1); 12 | 13 | public NamedThreadFactory(String name) { 14 | SecurityManager s = System.getSecurityManager(); 15 | threadGroup = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); 16 | if (null == name || "".equals(name.trim())) { 17 | name = "pool"; 18 | } 19 | namePrefix = name + "-" + poolNumber.getAndIncrement() + "-thread-"; 20 | } 21 | 22 | @Override 23 | public Thread newThread(Runnable r) { 24 | Thread t = new Thread(threadGroup, r, namePrefix + threadNumber.getAndIncrement(), 0); 25 | if (t.isDaemon()) 26 | t.setDaemon(false); 27 | if (t.getPriority() != Thread.NORM_PRIORITY) 28 | t.setPriority(Thread.NORM_PRIORITY); 29 | return t; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sql/cqrs.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE cqrs.`event_offset` ( 2 | `id` bigint NOT NULL AUTO_INCREMENT, 3 | `event_offset_id` bigint NOT NULL, 4 | `data_source_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, 5 | `table_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, 6 | PRIMARY KEY (`id`) 7 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; 8 | 9 | INSERT INTO `cqrs`.`event_offset`(`id`, `event_offset_id`, `data_source_name`, `table_name`) VALUES (1, 0, 'ds0', 'event_stream_0'); 10 | 11 | CREATE TABLE cqrs.`event_stream_0` ( 12 | `id` bigint NOT NULL AUTO_INCREMENT, 13 | `aggregate_root_type_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, 14 | `aggregate_root_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, 15 | `version` int NOT NULL, 16 | `command_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, 17 | `gmt_create` datetime NOT NULL, 18 | `events` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, 19 | PRIMARY KEY (`id`), 20 | UNIQUE KEY `uk_aggregate_id_command_id` (`aggregate_root_id`,`command_id`) USING BTREE, 21 | UNIQUE KEY `uk_aggregate_id_version` (`aggregate_root_id`,`version`) USING BTREE 22 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; 23 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/fixture/CanNotInstantiatedSPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi.fixture; 19 | 20 | import com.damon.cqrs.spi.Join; 21 | 22 | /** 23 | * can't instantiated test SPI class. 24 | */ 25 | @Join 26 | public final class CanNotInstantiatedSPI implements JdbcSPI { 27 | 28 | private CanNotInstantiatedSPI() { 29 | } 30 | 31 | @Override 32 | public String getClassName() { 33 | return "canNotInstantiatedSPI"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /cqrs-spi/src/main/java/com/damon/cqrs/spi/ExtensionFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi; 19 | 20 | /** 21 | * The interface Extension factory. 22 | */ 23 | @SPI("spi") 24 | public interface ExtensionFactory { 25 | 26 | /** 27 | * Gets Extension. 28 | * 29 | * @param the type parameter 30 | * @param key the key 31 | * @param clazz the clazz 32 | * @return the extension 33 | */ 34 | T getExtension(String key, Class clazz); 35 | } 36 | -------------------------------------------------------------------------------- /cqrs-spi/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.damon.cqrs 7 | cqrs 8 | ${revision} 9 | 10 | com.example 11 | cqrs-spi 12 | ${revision} 13 | cqrs-spi 14 | cqrs-spi 15 | 16 | 17 | org.apache.commons 18 | commons-lang3 19 | 20 | 21 | ch.qos.logback 22 | logback-core 23 | 24 | 25 | ch.qos.logback 26 | logback-classic 27 | 28 | 29 | ch.qos.logback 30 | logback-access 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/SpiExtensionFactoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi; 19 | 20 | import com.damon.cqrs.spi.fixture.MysqlSPI; 21 | import org.junit.Test; 22 | 23 | import static org.junit.Assert.assertNull; 24 | 25 | public final class SpiExtensionFactoryTest { 26 | 27 | @Test 28 | public void testNull() { 29 | SpiExtensionFactory spiExtensionFactory = new SpiExtensionFactory(); 30 | assertNull(spiExtensionFactory.getExtension("testNull", MysqlSPI.class)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/fixture/ArrayListSPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi.fixture; 19 | 20 | import com.damon.cqrs.spi.Join; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | /** 26 | * ArrayListSpi . 27 | */ 28 | @Join 29 | public class ArrayListSPI implements ListSPI { 30 | /** 31 | * Gets list. 32 | * 33 | * @return the list 34 | */ 35 | @Override 36 | public List getList() { 37 | return new ArrayList<>(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cqrs-spi/src/test/java/com/damon/cqrs/spi/fixture/LinkedListSPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi.fixture; 19 | 20 | import com.damon.cqrs.spi.Join; 21 | 22 | import java.util.LinkedList; 23 | import java.util.List; 24 | 25 | /** 26 | * LinkedList . 27 | */ 28 | @Join 29 | public class LinkedListSPI implements ListSPI { 30 | /** 31 | * Gets list. 32 | * 33 | * @return the list 34 | */ 35 | @Override 36 | public List getList() { 37 | return new LinkedList<>(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cqrs-message-kafka/src/test/java/KafkaProducerTest.java: -------------------------------------------------------------------------------- 1 | import org.apache.kafka.clients.producer.ProducerRecord; 2 | 3 | import java.util.Properties; 4 | 5 | /** 6 | * kafka生产者 7 | *

8 | * https://blog.csdn.net/chinawangfei/article/details/115468977 9 | */ 10 | public class KafkaProducerTest { 11 | // @Test 12 | public void test01() { 13 | 14 | 15 | Properties properties = new Properties(); 16 | properties.put("bootstrap.servers", "10.230.5.244:9092,10.230.4.87:9092,10.230.5.152:9092"); 17 | properties.put("acks", "all"); 18 | properties.put("retries", 0); 19 | properties.put("batch.size", 16384); 20 | properties.put("linger.ms", 1); 21 | properties.put("buffer.memory", 33554432); 22 | properties.put("key.serializer", "org.apache.mq.common.serialization.StringSerializer"); 23 | properties.put("value.serializer", "org.apache.mq.common.serialization.StringSerializer"); 24 | org.apache.kafka.clients.producer.KafkaProducer kafkaProducer = new org.apache.kafka.clients.producer.KafkaProducer(properties); 25 | for (int i = 1; i <= 3; i++) { 26 | //参数1:topic名, 参数2:消息文本; ProducerRecord多个重载的构造方法 27 | kafkaProducer.send(new ProducerRecord("test20200519", i + "", "message" + i)); 28 | System.out.println("message" + i); 29 | } 30 | kafkaProducer.flush(); 31 | kafkaProducer.close(); 32 | } 33 | } -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/aggregate/value_object/TicketBuyStatus.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.aggregate.value_object; 2 | 3 | import com.damon.cqrs.sample.train.aggregate.value_object.enum_type.SEAT_PROTECT_TYPE; 4 | import com.damon.cqrs.sample.train.aggregate.value_object.enum_type.TICKET_BUY_STATUS; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public class TicketBuyStatus { 10 | 11 | private final TICKET_BUY_STATUS stauts; 12 | 13 | private List userIds; 14 | 15 | private Map seatIndexs; 16 | 17 | public TicketBuyStatus(TICKET_BUY_STATUS stauts) { 18 | this.stauts = stauts; 19 | } 20 | 21 | public TicketBuyStatus(TICKET_BUY_STATUS stauts, List userIds, Map seatIndexs) { 22 | this.stauts = stauts; 23 | this.userIds = userIds; 24 | this.seatIndexs = seatIndexs; 25 | } 26 | 27 | public TICKET_BUY_STATUS getStauts() { 28 | return stauts; 29 | } 30 | 31 | public List getUserIds() { 32 | return userIds; 33 | } 34 | 35 | public void setUserIds(List userIds) { 36 | this.userIds = userIds; 37 | } 38 | 39 | 40 | public Map getSeatIndexs() { 41 | return seatIndexs; 42 | } 43 | 44 | public void setSeatIndexs(Map seatIndexs) { 45 | this.seatIndexs = seatIndexs; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /cqrs-api/src/main/java/com/damon/cqrs/exception/EventStoreException.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.exception; 2 | 3 | public class EventStoreException extends RuntimeException { 4 | 5 | /** 6 | * 7 | */ 8 | private static final long serialVersionUID = -6513851101874096469L; 9 | 10 | /** 11 | * 12 | */ 13 | public EventStoreException() { 14 | super(); 15 | // TODO Auto-generated constructor stub 16 | } 17 | 18 | /** 19 | * @param message 20 | * @param cause 21 | * @param enableSuppression 22 | * @param writableStackTrace 23 | */ 24 | public EventStoreException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 25 | super(message, cause, enableSuppression, writableStackTrace); 26 | // TODO Auto-generated constructor stub 27 | } 28 | 29 | /** 30 | * @param message 31 | * @param cause 32 | */ 33 | public EventStoreException(String message, Throwable cause) { 34 | super(message, cause); 35 | // TODO Auto-generated constructor stub 36 | } 37 | 38 | /** 39 | * @param message 40 | */ 41 | public EventStoreException(String message) { 42 | super(message); 43 | // TODO Auto-generated constructor stub 44 | } 45 | 46 | /** 47 | * @param cause 48 | */ 49 | public EventStoreException(Throwable cause) { 50 | super(cause); 51 | // TODO Auto-generated constructor stub 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /cqrs-api/src/main/java/com/damon/cqrs/exception/EventSendingException.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.exception; 2 | 3 | public class EventSendingException extends RuntimeException { 4 | 5 | /** 6 | * 7 | */ 8 | private static final long serialVersionUID = -6513851101874096469L; 9 | 10 | /** 11 | * 12 | */ 13 | public EventSendingException() { 14 | super(); 15 | // TODO Auto-generated constructor stub 16 | } 17 | 18 | /** 19 | * @param message 20 | * @param cause 21 | * @param enableSuppression 22 | * @param writableStackTrace 23 | */ 24 | public EventSendingException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 25 | super(message, cause, enableSuppression, writableStackTrace); 26 | // TODO Auto-generated constructor stub 27 | } 28 | 29 | /** 30 | * @param message 31 | * @param cause 32 | */ 33 | public EventSendingException(String message, Throwable cause) { 34 | super(message, cause); 35 | // TODO Auto-generated constructor stub 36 | } 37 | 38 | /** 39 | * @param message 40 | */ 41 | public EventSendingException(String message) { 42 | super(message); 43 | // TODO Auto-generated constructor stub 44 | } 45 | 46 | /** 47 | * @param cause 48 | */ 49 | public EventSendingException(Throwable cause) { 50 | super(cause); 51 | // TODO Auto-generated constructor stub 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/utils/GenericsUtils.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.utils; 2 | 3 | import java.lang.reflect.ParameterizedType; 4 | import java.lang.reflect.Type; 5 | 6 | 7 | @SuppressWarnings("rawtypes") 8 | public class GenericsUtils { 9 | /** 10 | * 通过反射,获得定义Class时声明的父类的范型参数的类型. 如public BookManager extends GenricManager 11 | * 12 | * @param clazz The class to introspect 13 | * @return the first generic declaration, or Object.class if cannot be determined 14 | */ 15 | 16 | public static Class getSuperClassGenricType(Class clazz) { 17 | return getSuperClassGenricType(clazz, 0); 18 | } 19 | 20 | /** 21 | * 通过反射,获得定义Class时声明的父类的范型参数的类型. 如public BookManager extends GenricManager 22 | * 23 | * @param clazz clazz The class to introspect 24 | * @param index the Index of the generic ddeclaration,start from 0. 25 | */ 26 | public static Class getSuperClassGenricType(Class clazz, int index) throws IndexOutOfBoundsException { 27 | Type genType = clazz.getGenericSuperclass(); 28 | if (!(genType instanceof ParameterizedType)) { 29 | return Object.class; 30 | } 31 | Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); 32 | if (index >= params.length || index < 0) { 33 | return Object.class; 34 | } 35 | if (!(params[index] instanceof Class)) { 36 | return Object.class; 37 | } 38 | return (Class) params[index]; 39 | } 40 | } -------------------------------------------------------------------------------- /cqrs-api/src/main/java/com/damon/cqrs/exception/AggregateEventConflictException.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.exception; 2 | 3 | public class AggregateEventConflictException extends RuntimeException { 4 | 5 | /** 6 | * 7 | */ 8 | private static final long serialVersionUID = -6513851101874096469L; 9 | private long aggregateId; 10 | private String aggregateType; 11 | 12 | /** 13 | * 14 | */ 15 | public AggregateEventConflictException() { 16 | super(); 17 | // TODO Auto-generated constructor stub 18 | } 19 | 20 | /** 21 | * @param message 22 | */ 23 | public AggregateEventConflictException(String message) { 24 | super(message); 25 | // TODO Auto-generated constructor stub 26 | } 27 | 28 | /** 29 | * @param cause 30 | */ 31 | public AggregateEventConflictException(long aggregateId, String aggregateType, Throwable cause) { 32 | // TODO Auto-generated constructor stub 33 | super(cause); 34 | this.aggregateId = aggregateId; 35 | this.aggregateType = aggregateType; 36 | } 37 | 38 | public long getAggregateId() { 39 | return aggregateId; 40 | } 41 | 42 | public void setAggregateId(long aggregateId) { 43 | this.aggregateId = aggregateId; 44 | } 45 | 46 | public String getAggregateType() { 47 | return aggregateType; 48 | } 49 | 50 | public void setAggregateType(String aggregateType) { 51 | this.aggregateType = aggregateType; 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /cqrs-spi/src/main/java/com/damon/cqrs/spi/SpiExtensionFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi; 19 | 20 | import java.util.Optional; 21 | 22 | /** 23 | * SpiExtensionFactory. 24 | */ 25 | @Join 26 | public class SpiExtensionFactory implements ExtensionFactory { 27 | 28 | @Override 29 | public T getExtension(final String key, final Class clazz) { 30 | return Optional.ofNullable(clazz) 31 | .filter(Class::isInterface) 32 | .filter(cls -> cls.isAnnotationPresent(SPI.class)) 33 | .map(ExtensionLoader::getExtensionLoader) 34 | .map(ExtensionLoader::getDefaultJoin) 35 | .orElse(null); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cqrs-spi/src/main/java/com/damon/cqrs/spi/SPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.damon.cqrs.spi; 19 | 20 | import java.lang.annotation.*; 21 | 22 | /** 23 | * SPI Extend the processing. 24 | * All spi system reference the apache implementation of 25 | * Apache Dubbo Common Extension. 26 | * 27 | * @see ExtensionFactory 28 | * @see ExtensionLoader 29 | */ 30 | @Documented 31 | @Retention(RetentionPolicy.RUNTIME) 32 | @Target(ElementType.TYPE) 33 | public @interface SPI { 34 | 35 | /** 36 | * Value string. 37 | * 38 | * @return the string 39 | */ 40 | String value() default ""; 41 | } 42 | -------------------------------------------------------------------------------- /cqrs-api/src/main/java/com/damon/cqrs/exception/DuplicateEventStreamException.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.exception; 2 | 3 | public class DuplicateEventStreamException extends RuntimeException { 4 | 5 | /** 6 | * 7 | */ 8 | private static final long serialVersionUID = -8170774802271181073L; 9 | 10 | /** 11 | * 12 | */ 13 | public DuplicateEventStreamException() { 14 | super(); 15 | // TODO Auto-generated constructor stub 16 | } 17 | 18 | /** 19 | * @param message 20 | * @param cause 21 | * @param enableSuppression 22 | * @param writableStackTrace 23 | */ 24 | public DuplicateEventStreamException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 25 | super(message, cause, enableSuppression, writableStackTrace); 26 | // TODO Auto-generated constructor stub 27 | } 28 | 29 | /** 30 | * @param message 31 | * @param cause 32 | */ 33 | public DuplicateEventStreamException(String message, Throwable cause) { 34 | super(message, cause); 35 | // TODO Auto-generated constructor stub 36 | } 37 | 38 | /** 39 | * @param message 40 | */ 41 | public DuplicateEventStreamException(String message) { 42 | super(message); 43 | // TODO Auto-generated constructor stub 44 | } 45 | 46 | /** 47 | * @param cause 48 | */ 49 | public DuplicateEventStreamException(Throwable cause) { 50 | super(cause); 51 | // TODO Auto-generated constructor stub 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/command/TicketProtectCancelCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.command; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | import com.damon.cqrs.sample.train.aggregate.value_object.enum_type.SEAT_TYPE; 5 | 6 | public class TicketProtectCancelCommand extends Command { 7 | private Integer startStationNumber; 8 | private Integer endStationNumber; 9 | private Boolean strict; 10 | private SEAT_TYPE seatType; 11 | 12 | /** 13 | * @param commandId 14 | * @param aggregateId 15 | */ 16 | public TicketProtectCancelCommand(long commandId, long aggregateId) { 17 | super(commandId, aggregateId); 18 | } 19 | 20 | public Integer getStartStationNumber() { 21 | return startStationNumber; 22 | } 23 | 24 | public void setStartStationNumber(Integer startStationNumber) { 25 | this.startStationNumber = startStationNumber; 26 | } 27 | 28 | public Integer getEndStationNumber() { 29 | return endStationNumber; 30 | } 31 | 32 | public void setEndStationNumber(Integer endStationNumber) { 33 | this.endStationNumber = endStationNumber; 34 | } 35 | 36 | public Boolean getStrict() { 37 | return strict; 38 | } 39 | 40 | public void setStrict(Boolean strict) { 41 | this.strict = strict; 42 | } 43 | 44 | public SEAT_TYPE getSeatType() { 45 | return seatType; 46 | } 47 | 48 | public void setSeatType(SEAT_TYPE seatType) { 49 | this.seatType = seatType; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/red_packet/api/command/RedPacketCreateCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.red_packet.api.command; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | 5 | import java.math.BigDecimal; 6 | 7 | public class RedPacketCreateCommand extends Command { 8 | 9 | /** 10 | * 11 | */ 12 | private static final long serialVersionUID = -2434884336800045268L; 13 | 14 | private BigDecimal money; 15 | 16 | private BigDecimal number; 17 | /** 18 | * 红包发起人id 19 | */ 20 | private Long sponsorId; 21 | 22 | private BigDecimal minMoney; 23 | 24 | /** 25 | * @param commandId 26 | * @param aggregateId 27 | */ 28 | public RedPacketCreateCommand(long commandId, long aggregateId) { 29 | super(commandId, aggregateId); 30 | } 31 | 32 | public BigDecimal getMoney() { 33 | return money; 34 | } 35 | 36 | public void setMoney(BigDecimal money) { 37 | this.money = money; 38 | } 39 | 40 | public BigDecimal getNumber() { 41 | return number; 42 | } 43 | 44 | public void setNumber(BigDecimal number) { 45 | this.number = number; 46 | } 47 | 48 | public Long getSponsorId() { 49 | return sponsorId; 50 | } 51 | 52 | public void setSponsorId(Long sponsorId) { 53 | this.sponsorId = sponsorId; 54 | } 55 | 56 | public BigDecimal getMinMoney() { 57 | return minMoney; 58 | } 59 | 60 | public void setMinMoney(BigDecimal minMoney) { 61 | this.minMoney = minMoney; 62 | } 63 | 64 | 65 | } 66 | -------------------------------------------------------------------------------- /cqrs-api/src/main/java/com/damon/cqrs/exception/AggregateProcessingTimeoutException.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.exception; 2 | 3 | public class AggregateProcessingTimeoutException extends RuntimeException { 4 | 5 | /** 6 | * 7 | */ 8 | private static final long serialVersionUID = -6513851101874096469L; 9 | 10 | /** 11 | * 12 | */ 13 | public AggregateProcessingTimeoutException() { 14 | super(); 15 | // TODO Auto-generated constructor stub 16 | } 17 | 18 | /** 19 | * @param message 20 | * @param cause 21 | * @param enableSuppression 22 | * @param writableStackTrace 23 | */ 24 | public AggregateProcessingTimeoutException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 25 | super(message, cause, enableSuppression, writableStackTrace); 26 | // TODO Auto-generated constructor stub 27 | } 28 | 29 | /** 30 | * @param message 31 | * @param cause 32 | */ 33 | public AggregateProcessingTimeoutException(String message, Throwable cause) { 34 | super(message, cause); 35 | // TODO Auto-generated constructor stub 36 | } 37 | 38 | /** 39 | * @param message 40 | */ 41 | public AggregateProcessingTimeoutException(String message) { 42 | super(message); 43 | // TODO Auto-generated constructor stub 44 | } 45 | 46 | /** 47 | * @param cause 48 | */ 49 | public AggregateProcessingTimeoutException(Throwable cause) { 50 | super(cause); 51 | // TODO Auto-generated constructor stub 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /cqrs-message-kafka/src/main/java/com/damon/cqrs/kafka/KafkaEventDispatch.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.kafka; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | import com.damon.cqrs.utils.NamedThreadFactory; 5 | import org.apache.kafka.clients.consumer.KafkaConsumer; 6 | import org.apache.kafka.common.PartitionInfo; 7 | 8 | import java.util.List; 9 | import java.util.Properties; 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | import java.util.function.Consumer; 13 | 14 | public class KafkaEventDispatch { 15 | private final ExecutorService executorService; 16 | private final String topic; 17 | private final Consumer>> consumer; 18 | private final Properties kafkaConsumerProperties; 19 | private final int threadSize; 20 | 21 | public KafkaEventDispatch(Properties kafkaConsumerProperties, String topic, Consumer>> consumer) { 22 | this.consumer = consumer; 23 | this.kafkaConsumerProperties = kafkaConsumerProperties; 24 | this.topic = topic; 25 | KafkaConsumer kafkaConsumer = new KafkaConsumer<>(kafkaConsumerProperties); 26 | List partitionInfos = kafkaConsumer.partitionsFor(topic); 27 | this.threadSize = partitionInfos.size(); 28 | executorService = Executors.newFixedThreadPool( 29 | threadSize, new NamedThreadFactory("event-thread-") 30 | ); 31 | } 32 | 33 | public void start() { 34 | for (int i = 0; i < threadSize; i++) { 35 | executorService.submit(new KafkaMessageHandler(consumer, topic, kafkaConsumerProperties)); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /cqrs-message-kafka/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | cqrs 7 | com.damon.cqrs 8 | ${revision} 9 | 10 | 4.0.0 11 | 12 | cqrs-message-kafka 13 | 14 | 15 | 16 | org.projectlombok 17 | lombok 18 | 19 | 20 | com.damon.cqrs 21 | cqrs-core 22 | 23 | 24 | org.apache.kafka 25 | kafka-clients 26 | 27 | 28 | com.alipay.sofa 29 | bolt 30 | 1.6.6 31 | 32 | 33 | com.caucho 34 | hessian 35 | 4.0.63 36 | 37 | 38 | com.fasterxml.jackson.core 39 | jackson-databind 40 | 2.14.2 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /cqrs-event-mysql/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 4.0.0 6 | 7 | com.damon.cqrs 8 | cqrs 9 | ${revision} 10 | 11 | cqrs-event-mysql 12 | cqrs-event-store-mysql 13 | 14 | UTF-8 15 | 16 | 17 | 18 | com.damon.cqrs 19 | cqrs-core 20 | 21 | 22 | 23 | com.alibaba 24 | fastjson 25 | 26 | 27 | org.projectlombok 28 | lombok 29 | 30 | 31 | commons-dbutils 32 | commons-dbutils 33 | 34 | 35 | com.zaxxer 36 | HikariCP 37 | 38 | 39 | mysql 40 | mysql-connector-java 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/cache/DefaultAggregateCaffeineCache.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.cache; 2 | 3 | import com.damon.cqrs.domain.AggregateRoot; 4 | import com.github.benmanes.caffeine.cache.Cache; 5 | import com.github.benmanes.caffeine.cache.Caffeine; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * 基于caffeine聚合根缓存 12 | * 13 | * @author xianpinglu 14 | */ 15 | @Slf4j 16 | public class DefaultAggregateCaffeineCache implements IAggregateCache { 17 | 18 | private final Cache aggregateCache; 19 | 20 | /** 21 | * @param cacheMaximumSize 最多能够缓存多少聚合个数 22 | * @param expireTime 有效时间(分钟) 23 | */ 24 | public DefaultAggregateCaffeineCache(int cacheMaximumSize, int expireTime) { 25 | aggregateCache = Caffeine.newBuilder().expireAfterWrite(expireTime, TimeUnit.MINUTES) 26 | .maximumSize(cacheMaximumSize).removalListener((key, value, cause) -> { 27 | Long aggregateId = (Long) key; 28 | AggregateRoot aggregate = (AggregateRoot) value; 29 | log.info("aggregate id : {}, aggregate type : {}, version:{}, expired.", 30 | aggregateId, aggregate.getClass().getTypeName(), aggregate.getVersion() 31 | ); 32 | }).build(); 33 | } 34 | 35 | @Override 36 | public void update(long id, AggregateRoot aggregate) { 37 | aggregateCache.put(id, aggregate); 38 | } 39 | 40 | @Override 41 | @SuppressWarnings("unchecked") 42 | public T get(long id) { 43 | return (T) aggregateCache.getIfPresent(id); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### cqrs 2 | 3 | cqrs是基于事件回溯的高性能架构,主要针对单个领域业务高度复杂且需要高性能的业务场景,可以说它能够作为高并发系统的通用解决方案。例如:秒杀、抢红包、12306卖票等。基于事件溯源,实现cqrs最复杂的模型, 4 | 通过事件是追加的特性,然后结合事件批量提交的手段,避免在高并发更新时多个线程update带来的严重锁冲突问题,从而来实现高性能。 5 | 支持聚合回溯,包括扩容、缩容聚合冲突事件回溯、聚合根在内存、聚合根快照、聚合事件组提交,可集成dubbo、spring 6 | cloud。注意:并未支持saga业务编排,由业务方自己去编排。 7 | 8 | ### 架构概述 9 | 10 | ![架构图](image/71895e721aced94523d752f63e5cb0c.png) 11 | 12 | ### 使用示例: 13 | 14 | //1.初始化商品库存管理服务 15 | 16 | GoodsStockService service = new GoodsStockService(committingService); 17 | 18 | //2.初始化商品 19 | 20 | GoodsAddCommand command = new GoodsAddCommand(IdWorker.getId(), 2, "iphone 6 plus", 1000); 21 | 22 | Goods goods1 = service.process(command, () -> new Goods(2, command.getName(), command.getCount())).join(); 23 | 24 | //3.库存+1 25 | 26 | GoodsStockAddCommand command = new GoodsStockAddCommand(IdWorker.getId(), 2); 27 | 28 | Goods goods2 = service.process(command, goods -> goods.addStock(1)).join(); 29 | 30 | ### 注意事项 31 | 32 | 1.如果使用dubbo、spring cloud负载均衡策略请选择一致性hash,这样可以减少在集群扩容、缩容聚合根回溯的成本。 33 | 34 | 2.关闭dubbo、spring cloud的失败重试。 35 | 36 | 3.Dubbo服务抛出该异常AggregateEventConflictException,客户端可以重新发起请求。( 37 | 出现此异常的原因是当前聚合根在多个实例中存在(集群扩容时),可以捕获此异常然后重新在client发起调用,当前的请求会负载到新的实例上。) 38 | 39 | 4.mysql 需要开启 rewriteBatchedStatements 批量操作选项,否则性能不佳。 40 | 41 | ### 测试报告 42 | 43 | CPU:I7-3740QM(4核8线程) 24G内存 mysql 5.7 ssd(早期固态硬盘) jdk1.8 44 | 45 | 性能数据: 46 | 47 | 商品添加:6.5K TPS/s 48 | 49 | 单个商品库存添加:14K TPS/S 50 | 51 | 三个商品库存添加:30K TPS/S mysql cpu:18% mysql内存占用:300M , jvm cpu: 20% jvm 内存占用:1.8G 52 | 53 | ### 案例 54 | 55 | 1.如何通过事件溯源实现百万TPS全内存撮合交易引擎
56 | https://blog.csdn.net/luxianping/article/details/143323082 57 | 58 | 2.如何通过基于事件溯源架构高效解决12306的库存问题
59 | https://blog.csdn.net/luxianping/article/details/122857386 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/aggregate/value_object/UserSeatInfo.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.aggregate.value_object; 2 | 3 | import com.damon.cqrs.sample.train.aggregate.value_object.enum_type.SEAT_PROTECT_TYPE; 4 | import com.damon.cqrs.sample.train.aggregate.value_object.enum_type.SEAT_TYPE; 5 | 6 | /** 7 | * 用户车次坐席信息 8 | */ 9 | public class UserSeatInfo { 10 | 11 | private Integer startStationNumber; 12 | 13 | private Integer endStationNumber; 14 | 15 | private Integer seatIndex; 16 | 17 | private SEAT_PROTECT_TYPE seatProtectType; 18 | 19 | private SEAT_TYPE seatType; 20 | 21 | public Integer getStartStationNumber() { 22 | return startStationNumber; 23 | } 24 | 25 | public void setStartStationNumber(Integer startStationNumber) { 26 | this.startStationNumber = startStationNumber; 27 | } 28 | 29 | public Integer getEndStationNumber() { 30 | return endStationNumber; 31 | } 32 | 33 | public void setEndStationNumber(Integer endStationNumber) { 34 | this.endStationNumber = endStationNumber; 35 | } 36 | 37 | public Integer getSeatIndex() { 38 | return seatIndex; 39 | } 40 | 41 | public void setSeatIndex(Integer seatIndex) { 42 | this.seatIndex = seatIndex; 43 | } 44 | 45 | public SEAT_PROTECT_TYPE getSeatProtectType() { 46 | return seatProtectType; 47 | } 48 | 49 | public void setSeatProtectType(SEAT_PROTECT_TYPE seatProtectType) { 50 | this.seatProtectType = seatProtectType; 51 | } 52 | 53 | public SEAT_TYPE getSeatType() { 54 | return seatType; 55 | } 56 | 57 | public void setSeatType(SEAT_TYPE seatType) { 58 | this.seatType = seatType; 59 | } 60 | } -------------------------------------------------------------------------------- /cqrs-message-kafka/src/main/java/com/damon/cqrs/kafka/KafkaSendService.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.kafka; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.damon.cqrs.event.EventSendingContext; 5 | import com.damon.cqrs.event.ISendMessageService; 6 | import com.damon.cqrs.kafka.config.KafkaProducerConfig; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.kafka.clients.producer.KafkaProducer; 9 | import org.apache.kafka.clients.producer.ProducerRecord; 10 | 11 | import java.util.List; 12 | import java.util.Properties; 13 | 14 | 15 | @Slf4j 16 | public class KafkaSendService implements ISendMessageService { 17 | private final String topic; 18 | private final KafkaProducer kafkaProducer; 19 | 20 | public KafkaSendService(KafkaProducerConfig config) { 21 | this.topic = config.getTopic(); 22 | Properties properties = new Properties(); 23 | properties.put("bootstrap.servers", config.getBootstrapServers()); 24 | properties.put("acks", "all"); 25 | properties.put("retries", 0); 26 | properties.put("batch.size", 16384); 27 | properties.put("linger.ms", 20); 28 | properties.put("buffer.memory", 33554432); 29 | properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); 30 | properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); 31 | kafkaProducer = new KafkaProducer<>(properties); 32 | } 33 | 34 | @Override 35 | public void sendMessage(List contexts) { 36 | contexts.forEach(context -> { 37 | kafkaProducer.send(new ProducerRecord<>(topic, context.getAggregateId() + "", JSONObject.toJSONString(context.getEvents()))); 38 | }); 39 | kafkaProducer.flush(); 40 | } 41 | } -------------------------------------------------------------------------------- /cqrs-api/src/main/java/com/damon/cqrs/domain/Event.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.domain; 2 | 3 | import java.io.Serializable; 4 | import java.time.ZonedDateTime; 5 | 6 | /** 7 | * 注意:如果子类实现新的构造方法,子类一定要实现一个无参构造方法,否则事件在JSON序列化时会导致丢失Event的数据。 8 | * 9 | * @author xianping_lu 10 | */ 11 | public abstract class Event implements Serializable { 12 | /** 13 | * 14 | */ 15 | private static final long serialVersionUID = -879757997924919709L; 16 | private String eventType; 17 | private long aggregateId; 18 | private String aggregateType; 19 | private ZonedDateTime timestamp; 20 | private int version; 21 | 22 | public Event() { 23 | this.eventType = this.getClass().getTypeName(); 24 | this.timestamp = ZonedDateTime.now(); 25 | } 26 | 27 | public long getAggregateId() { 28 | return aggregateId; 29 | } 30 | 31 | public Event setAggregateId(long aggregateId) { 32 | this.aggregateId = aggregateId; 33 | return this; 34 | } 35 | 36 | public ZonedDateTime getTimestamp() { 37 | return this.timestamp; 38 | } 39 | 40 | public Event setTimestamp(ZonedDateTime timestamp) { 41 | this.timestamp = timestamp; 42 | return this; 43 | } 44 | 45 | public int getVersion() { 46 | return version; 47 | } 48 | 49 | public Event setVersion(int version) { 50 | this.version = version; 51 | return this; 52 | } 53 | 54 | public String getAggregateType() { 55 | return aggregateType; 56 | } 57 | 58 | public Event setAggregateType(String aggregateType) { 59 | this.aggregateType = aggregateType; 60 | return this; 61 | } 62 | 63 | public String getEventType() { 64 | return eventType; 65 | } 66 | 67 | public void setEventType(String eventType) { 68 | this.eventType = eventType; 69 | } 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/aggregate/value_object/TrainCarriage.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.aggregate.value_object; 2 | 3 | import com.damon.cqrs.domain.ValueObject; 4 | import com.damon.cqrs.sample.train.aggregate.value_object.enum_type.SEAT_TYPE; 5 | 6 | /** 7 | * 火车车厢 8 | */ 9 | //@Builder 10 | public class TrainCarriage implements ValueObject { 11 | 12 | /** 13 | * 车厢编号 14 | */ 15 | private Integer number; 16 | /** 17 | * 车站座位开始编号 18 | */ 19 | private Integer startNumber; 20 | /** 21 | * 车站座位结束编号 22 | */ 23 | private Integer endNumber; 24 | /** 25 | * 车厢类型 0 特等座 1 一等做 2 二等座 26 | */ 27 | private SEAT_TYPE seatType; 28 | 29 | // private Integer number; 30 | public TrainCarriage(Integer number, Integer startNumber, Integer endNumber, SEAT_TYPE seatType) { 31 | this.number = number; 32 | this.startNumber = startNumber; 33 | this.endNumber = endNumber; 34 | this.seatType = seatType; 35 | } 36 | 37 | public TrainCarriage() { 38 | } 39 | 40 | public Integer getStartNumber() { 41 | return startNumber; 42 | } 43 | 44 | public void setStartNumber(Integer startNumber) { 45 | this.startNumber = startNumber; 46 | } 47 | 48 | public Integer getEndNumber() { 49 | return endNumber; 50 | } 51 | 52 | public void setEndNumber(Integer endNumber) { 53 | this.endNumber = endNumber; 54 | } 55 | 56 | public Integer getNumber() { 57 | return number; 58 | } 59 | 60 | public void setNumber(Integer number) { 61 | this.number = number; 62 | } 63 | 64 | public SEAT_TYPE getSeatType() { 65 | return seatType; 66 | } 67 | 68 | public void setSeatType(SEAT_TYPE seatType) { 69 | this.seatType = seatType; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /cqrs-api/src/main/java/com/damon/cqrs/domain/Command.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.domain; 2 | 3 | import java.io.Serializable; 4 | import java.util.Map; 5 | 6 | import static com.google.common.base.Preconditions.checkNotNull; 7 | 8 | public abstract class Command implements Serializable { 9 | 10 | /** 11 | * 12 | */ 13 | private static final long serialVersionUID = -2869549269787386287L; 14 | 15 | private Long commandId; 16 | 17 | private Long aggregateId; 18 | 19 | private Map shardingParams; 20 | 21 | 22 | public Command(Long commandId, Long aggregateId, Map shardingParams) { 23 | checkNotNull(commandId); 24 | checkNotNull(aggregateId); 25 | this.commandId = commandId; 26 | this.aggregateId = aggregateId; 27 | this.shardingParams = shardingParams; 28 | } 29 | 30 | /** 31 | * @param commandId 32 | * @param aggregateId 33 | */ 34 | public Command(Long commandId, Long aggregateId) { 35 | checkNotNull(commandId); 36 | checkNotNull(aggregateId); 37 | this.commandId = commandId; 38 | this.aggregateId = aggregateId; 39 | } 40 | 41 | public Long getCommandId() { 42 | return commandId; 43 | } 44 | 45 | public void setCommandId(long commandId) { 46 | this.commandId = commandId; 47 | } 48 | 49 | public Long getAggregateId() { 50 | return aggregateId; 51 | } 52 | 53 | public void setAggregateId(long aggregateId) { 54 | this.aggregateId = aggregateId; 55 | } 56 | 57 | public Map getShardingParams() { 58 | return shardingParams; 59 | } 60 | 61 | public void setShardingParams(Map shardingParams) { 62 | this.shardingParams = shardingParams; 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | return Long.hashCode(aggregateId); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/command/TicketBuyCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.command; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | import com.damon.cqrs.sample.train.aggregate.value_object.enum_type.SEAT_TYPE; 5 | 6 | import java.util.List; 7 | 8 | public class TicketBuyCommand extends Command { 9 | 10 | private List userIds; 11 | private Integer startStationNumber; 12 | private Integer endStationNumber; 13 | private SEAT_TYPE seatType; 14 | /** 15 | * 选座 0-10 分别对应 n排 ABCDEF n+1排 ABCDEF 16 | */ 17 | private List seatIndexs; 18 | 19 | 20 | /** 21 | * @param commandId 22 | * @param aggregateId 23 | */ 24 | public TicketBuyCommand(long commandId, long aggregateId) { 25 | super(commandId, aggregateId); 26 | } 27 | 28 | public List getUserIds() { 29 | return userIds; 30 | } 31 | 32 | public void setUserIds(List userIds) { 33 | this.userIds = userIds; 34 | } 35 | 36 | public Integer getStartStationNumber() { 37 | return startStationNumber; 38 | } 39 | 40 | public void setStartStationNumber(Integer startStationNumber) { 41 | this.startStationNumber = startStationNumber; 42 | } 43 | 44 | public Integer getEndStationNumber() { 45 | return endStationNumber; 46 | } 47 | 48 | public void setEndStationNumber(Integer endStationNumber) { 49 | this.endStationNumber = endStationNumber; 50 | } 51 | 52 | public SEAT_TYPE getSeatType() { 53 | return seatType; 54 | } 55 | 56 | public void setSeatType(SEAT_TYPE seatType) { 57 | this.seatType = seatType; 58 | } 59 | 60 | public List getSeatIndexs() { 61 | return seatIndexs; 62 | } 63 | 64 | public void setSeatIndexs(List seatIndexs) { 65 | this.seatIndexs = seatIndexs; 66 | } 67 | } 68 | 69 | 70 | -------------------------------------------------------------------------------- /cqrs-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.damon.cqrs 8 | cqrs 9 | ${revision} 10 | 11 | cqrs-api 12 | cqrs-api 13 | 14 | UTF-8 15 | 16 | 17 | 18 | com.google.guava 19 | guava 20 | 21 | 22 | org.projectlombok 23 | lombok 24 | 25 | 26 | ch.qos.logback 27 | logback-access 28 | 29 | 30 | ch.qos.logback 31 | logback-classic 32 | 33 | 34 | ch.qos.logback 35 | logback-core 36 | 37 | 38 | 39 | 40 | 41 | org.apache.maven.plugins 42 | maven-compiler-plugin 43 | 3.11.0 44 | 45 | ${java.version} 46 | ${java.version} 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/event/TicketCanceledEvent.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.event; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | import com.damon.cqrs.sample.train.aggregate.value_object.enum_type.SEAT_PROTECT_TYPE; 5 | import com.damon.cqrs.sample.train.aggregate.value_object.enum_type.SEAT_TYPE; 6 | 7 | public class TicketCanceledEvent extends Event { 8 | private Long userId; 9 | private Integer startStationNumber; 10 | private Integer endStationNumber; 11 | private Integer seatIndex; 12 | private SEAT_PROTECT_TYPE seatProtectType; 13 | 14 | private SEAT_TYPE seatType; 15 | 16 | public TicketCanceledEvent() { 17 | 18 | } 19 | 20 | public Long getUserId() { 21 | return userId; 22 | } 23 | 24 | public void setUserId(Long userId) { 25 | this.userId = userId; 26 | } 27 | 28 | public Integer getStartStationNumber() { 29 | return startStationNumber; 30 | } 31 | 32 | public void setStartStationNumber(Integer startStationNumber) { 33 | this.startStationNumber = startStationNumber; 34 | } 35 | 36 | public Integer getEndStationNumber() { 37 | return endStationNumber; 38 | } 39 | 40 | public void setEndStationNumber(Integer endStationNumber) { 41 | this.endStationNumber = endStationNumber; 42 | } 43 | 44 | public Integer getSeatIndex() { 45 | return seatIndex; 46 | } 47 | 48 | public void setSeatIndex(Integer seatIndex) { 49 | this.seatIndex = seatIndex; 50 | } 51 | 52 | public SEAT_PROTECT_TYPE getSeatProtectType() { 53 | return seatProtectType; 54 | } 55 | 56 | public void setSeatProtectType(SEAT_PROTECT_TYPE seatProtectType) { 57 | this.seatProtectType = seatProtectType; 58 | } 59 | 60 | public SEAT_TYPE getSeatType() { 61 | return seatType; 62 | } 63 | 64 | public void setSeatType(SEAT_TYPE seatType) { 65 | this.seatType = seatType; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/domain/MettingCommandService.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.domain; 2 | 3 | import com.damon.cqrs.command.CommandService; 4 | import com.damon.cqrs.config.CqrsConfig; 5 | import com.damon.cqrs.sample.metting.api.IMettingCommandService; 6 | import com.damon.cqrs.sample.metting.api.command.MettingCancelCommand; 7 | import com.damon.cqrs.sample.metting.api.command.MettingDTO; 8 | import com.damon.cqrs.sample.metting.api.command.MettingGetCommand; 9 | import com.damon.cqrs.sample.metting.api.command.MettingReserveCommand; 10 | import com.damon.cqrs.sample.metting.domain.aggregate.CancelReservationStatusEnum; 11 | import com.damon.cqrs.sample.metting.domain.aggregate.MeetingId; 12 | import com.damon.cqrs.sample.metting.domain.aggregate.Metting; 13 | import com.damon.cqrs.sample.metting.domain.aggregate.ReseveStatus; 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | @Slf4j 17 | public class MettingCommandService extends CommandService implements IMettingCommandService { 18 | 19 | public MettingCommandService(CqrsConfig cqrsConfig) { 20 | super(cqrsConfig); 21 | } 22 | 23 | @Override 24 | public ReseveStatus reserve(MettingReserveCommand command) { 25 | return super.process(command, metting -> metting.reserve(command)).join(); 26 | } 27 | 28 | @Override 29 | public CancelReservationStatusEnum cancel(MettingCancelCommand cancel) { 30 | return super.process(cancel, metting -> 31 | metting.cancel(cancel) 32 | ).join(); 33 | } 34 | 35 | @Override 36 | public MettingDTO get(MettingGetCommand get) { 37 | return super.process(get, metting -> 38 | new MettingDTO(metting.getSchedule(), metting.getMeetingDate(), metting.getReserveRecord()) 39 | ).join(); 40 | } 41 | 42 | @Override 43 | public Metting getAggregateSnapshot(long aggregateId, Class classes) { 44 | Metting metting = new Metting(new MeetingId(aggregateId)); 45 | return metting; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | logback 4 | 5 | 6 | 7 | 8 | debug 9 | 10 | 11 | %d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n 12 | 13 | 14 | 15 | 16 | 17 | ${log.path}/log.log 18 | 19 | ${log.path}/log-%d{yyyy-MM-dd}.log 20 | 21 | 22 | 23 | %date %level [%thread] %logger{36} [%file : %line] %msg%n 24 | 25 | 26 | 27 | 28 | 29 | ${log.path}/error.log 30 | 31 | ${log.path}/error-%d{yyyy-MM-dd}.log 32 | 33 | 34 | 35 | %date %level [%thread] %logger{36} [%file : %line] %msg%n 36 | 37 | 38 | 39 | 40 | ERROR 41 | ACCEPT 42 | DENY 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /cqrs-api/src/main/java/com/damon/cqrs/exception/AggregateCommandConflictException.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.exception; 2 | 3 | public class AggregateCommandConflictException extends RuntimeException { 4 | 5 | /** 6 | * 7 | */ 8 | private static final long serialVersionUID = -6513851101874096469L; 9 | private long aggregateId; 10 | private String aggregateType; 11 | private long commandId; 12 | 13 | /** 14 | * 15 | */ 16 | public AggregateCommandConflictException() { 17 | super(); 18 | // TODO Auto-generated constructor stub 19 | } 20 | 21 | /** 22 | * @param message 23 | * @param cause 24 | * @param enableSuppression 25 | * @param writableStackTrace 26 | */ 27 | public AggregateCommandConflictException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 28 | super(message, cause, enableSuppression, writableStackTrace); 29 | // TODO Auto-generated constructor stub 30 | } 31 | 32 | /** 33 | * @param message 34 | * @param cause 35 | */ 36 | public AggregateCommandConflictException(String message, Throwable cause) { 37 | super(message, cause); 38 | // TODO Auto-generated constructor stub 39 | } 40 | 41 | /** 42 | * @param message 43 | */ 44 | public AggregateCommandConflictException(String message) { 45 | super(message); 46 | // TODO Auto-generated constructor stub 47 | } 48 | 49 | /** 50 | * @param cause 51 | */ 52 | public AggregateCommandConflictException(long aggregateId, String aggregateType, long commandId, Throwable cause) { 53 | super(cause); 54 | this.aggregateId = aggregateId; 55 | this.aggregateType = aggregateType; 56 | this.commandId = commandId; 57 | // TODO Auto-generated constructor stub 58 | } 59 | 60 | public long getAggregateId() { 61 | return aggregateId; 62 | } 63 | 64 | public String getAggregateType() { 65 | return aggregateType; 66 | } 67 | 68 | public long getCommandId() { 69 | return commandId; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/command/ICommandService.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.command; 2 | 3 | import com.damon.cqrs.domain.AggregateRoot; 4 | import com.damon.cqrs.domain.Command; 5 | 6 | import java.util.concurrent.CompletableFuture; 7 | import java.util.function.Function; 8 | import java.util.function.Supplier; 9 | 10 | /** 11 | * 聚合领域服务抽象类 12 | *

13 | * 可以在此服务上封装dubbo、spring cloud 微服务框架。 14 | *

15 | * 注意:负载均衡需要采用hash机制,建议使用一致性hash,当集群扩容、缩容时对聚合根的恢复影响较小。 16 | * 17 | * @author xianping_lu 18 | */ 19 | public interface ICommandService { 20 | /** 21 | * @param command 22 | * @param supplier 23 | * @param lockWaitingTime 获取锁等待时间(毫秒) 24 | * @return 25 | */ 26 | CompletableFuture process(final Command command, final Supplier supplier, Long lockWaitingTime); 27 | 28 | /** 29 | * @param 30 | * @param command 31 | * @param function 32 | * @param lockWaitingTime 获取锁等待时间(毫秒) 33 | * @return 34 | */ 35 | CompletableFuture process(final Command command, final Function function, Long lockWaitingTime); 36 | 37 | /** 38 | * 获取聚合快照,用于加速聚合回溯(对于聚合存在的生命周期特别长且修改特别频繁时需要实现) 39 | *

40 | * 逻辑:先判断是否存在聚合快照,如果不存在聚合快照,从Q端恢复聚合。 41 | * 42 | * @param aggregateId 43 | * @param classes 44 | * @return 45 | */ 46 | default T getAggregateSnapshot(long aggregateId, Class classes) { 47 | return null; 48 | } 49 | 50 | /** 51 | * 保存聚合快照 52 | *

53 | * 可以保存到类似redis高性能缓存中(不用担心丢失,Q端、Event库中都存有聚合数据信息) 54 | * 55 | * @param aggregate 56 | * @return 57 | */ 58 | default CompletableFuture saveAggregateSnapshot(T aggregate) { 59 | return CompletableFuture.completedFuture(true); 60 | } 61 | 62 | /** 63 | * 创建聚合根快照 64 | * 65 | * @param aggregate 66 | * @return 67 | */ 68 | default T createAggregateSnapshot(T aggregate) { 69 | return null; 70 | } 71 | 72 | /** 73 | * 聚合根快照创建周期(单位秒),小于0不创建快照 74 | * 75 | * @return 76 | */ 77 | default long snapshotCycle() { 78 | return -1; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/event/TicketProtectSucceedEvent.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.event; 2 | 3 | import com.damon.cqrs.domain.Event; 4 | import com.damon.cqrs.sample.train.aggregate.value_object.enum_type.SEAT_TYPE; 5 | 6 | public class TicketProtectSucceedEvent extends Event { 7 | private Integer startStationNumber; 8 | private Integer endStationNumber; 9 | private Integer protectCanBuyTicketCount; 10 | /** 11 | * 保留的座位票索引,需要用 BitSet.valueOf(arr)反解析回来 12 | */ 13 | private long[] protectSeatIndex; 14 | 15 | private Integer maxCanBuyTicketCount; 16 | 17 | private SEAT_TYPE seatType; 18 | 19 | 20 | public TicketProtectSucceedEvent() { 21 | } 22 | 23 | public Integer getProtectCanBuyTicketCount() { 24 | return protectCanBuyTicketCount; 25 | } 26 | 27 | public void setProtectCanBuyTicketCount(Integer protectCanBuyTicketCount) { 28 | this.protectCanBuyTicketCount = protectCanBuyTicketCount; 29 | } 30 | 31 | public Integer getStartStationNumber() { 32 | return startStationNumber; 33 | } 34 | 35 | public void setStartStationNumber(Integer startStationNumber) { 36 | this.startStationNumber = startStationNumber; 37 | } 38 | 39 | public Integer getEndStationNumber() { 40 | return endStationNumber; 41 | } 42 | 43 | public void setEndStationNumber(Integer endStationNumber) { 44 | this.endStationNumber = endStationNumber; 45 | } 46 | 47 | public long[] getProtectSeatIndex() { 48 | return protectSeatIndex; 49 | } 50 | 51 | public void setProtectSeatIndex(long[] protectSeatIndex) { 52 | this.protectSeatIndex = protectSeatIndex; 53 | } 54 | 55 | public Integer getMaxCanBuyTicketCount() { 56 | return maxCanBuyTicketCount; 57 | } 58 | 59 | public void setMaxCanBuyTicketCount(Integer maxCanBuyTicketCount) { 60 | this.maxCanBuyTicketCount = maxCanBuyTicketCount; 61 | } 62 | 63 | public SEAT_TYPE getSeatType() { 64 | return seatType; 65 | } 66 | 67 | public void setSeatType(SEAT_TYPE seatType) { 68 | this.seatType = seatType; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/api/command/MettingReserveCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting.api.command; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | import com.damon.cqrs.sample.metting.domain.aggregate.MettingTime; 5 | import lombok.NonNull; 6 | 7 | public class MettingReserveCommand extends Command { 8 | 9 | private Long userId; 10 | private String mettingDate; 11 | private MettingTime mettingTime; 12 | private String mettingTopic; 13 | private String mettingContent; 14 | private String attachmentUrl; 15 | 16 | private String mettingNumber; 17 | 18 | public MettingReserveCommand(@NonNull Long commandId, 19 | @NonNull Long aggregateId, 20 | @NonNull Long userId, 21 | @NonNull MettingTime mettingTime, 22 | @NonNull String mettingTopic, 23 | @NonNull String mettingDate, 24 | @NonNull String mettingNumber, 25 | String mettingContent, 26 | String attachmentUrl 27 | ) { 28 | super(commandId, aggregateId); 29 | this.mettingTime = mettingTime; 30 | this.userId = userId; 31 | this.mettingTopic = mettingTopic; 32 | this.mettingContent = mettingContent; 33 | this.attachmentUrl = attachmentUrl; 34 | this.mettingDate = mettingDate; 35 | this.mettingNumber = mettingNumber; 36 | } 37 | 38 | public MettingTime getMettingTime() { 39 | return mettingTime; 40 | } 41 | 42 | public String getMettingTopic() { 43 | return mettingTopic; 44 | } 45 | 46 | public String getMettingContent() { 47 | return mettingContent; 48 | } 49 | 50 | public String getAttachmentUrl() { 51 | return attachmentUrl; 52 | } 53 | 54 | public Long getUserId() { 55 | return userId; 56 | } 57 | 58 | public String getMettingDate() { 59 | return mettingDate; 60 | } 61 | 62 | public String getMettingNumber() { 63 | return mettingNumber; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/event/AggregateEventAppendResult.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.event; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | @Data 10 | public class AggregateEventAppendResult { 11 | 12 | private List succeedResults = Collections.synchronizedList(new ArrayList<>()); 13 | 14 | private List dulicateCommandResults = Collections.synchronizedList(new ArrayList<>()); 15 | 16 | private List duplicateEventResults = Collections.synchronizedList(new ArrayList<>()); 17 | 18 | private List exceptionResults = Collections.synchronizedList(new ArrayList<>()); 19 | 20 | public void addSuccedResult(SucceedResult result) { 21 | succeedResults.add(result); 22 | } 23 | 24 | public void addDulicateCommandResult(DulicateCommandResult result) { 25 | dulicateCommandResults.add(result); 26 | } 27 | 28 | public void addDuplicateEventResult(DuplicateEventResult result) { 29 | duplicateEventResults.add(result); 30 | } 31 | 32 | public void addExceptionResult(ExceptionResult result) { 33 | exceptionResults.add(result); 34 | } 35 | 36 | @Data 37 | public static class SucceedResult { 38 | 39 | private Long aggregateId; 40 | 41 | private String aggregateType; 42 | 43 | private Long commandId; 44 | 45 | private Integer version; 46 | 47 | } 48 | 49 | @Data 50 | public static class DulicateCommandResult { 51 | 52 | private String aggregateType; 53 | 54 | private Long aggreateId; 55 | 56 | private Throwable throwable; 57 | 58 | private String commandId; 59 | } 60 | 61 | @Data 62 | public static class DuplicateEventResult { 63 | 64 | private Long aggreateId; 65 | 66 | private String aggregateType; 67 | 68 | private Throwable throwable; 69 | 70 | } 71 | 72 | @Data 73 | public static class ExceptionResult { 74 | 75 | private Long aggreateId; 76 | 77 | private String aggregateType; 78 | 79 | private Throwable throwable; 80 | 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /cqrs-core/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | logback 4 | 5 | 6 | 7 | 8 | debug 9 | 10 | 11 | %d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n 12 | 13 | 14 | 15 | 16 | 17 | ${log.path}/log.log 18 | 19 | ${log.path}/log-%d{yyyy-MM-dd}.log 20 | 21 | 22 | 23 | %date %level [%thread] %logger{36} [%file : %line] %msg%n 24 | 25 | 26 | 27 | 28 | 29 | ${log.path}/error.log 30 | 31 | ${log.path}/error-%d{yyyy-MM-dd}.log 32 | 33 | 34 | 35 | %date %level [%thread] %logger{36} [%file : %line] %msg%n 36 | 37 | 38 | 39 | 40 | ERROR 41 | ACCEPT 42 | DENY 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/command/TicketProtectCommand.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.command; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | import com.damon.cqrs.sample.train.aggregate.value_object.enum_type.SEAT_TYPE; 5 | 6 | public class TicketProtectCommand extends Command { 7 | private Integer startStationNumber; 8 | private Integer endStationNumber; 9 | /** 10 | * 站点与站点间保留票数(最少可以卖多少张票) 11 | */ 12 | private Integer minCanBuyTicketCount; 13 | /** 14 | * 站点与站点间最多可卖票数 15 | */ 16 | private Integer maxCanBuyTicketCount; 17 | 18 | private Boolean strict; 19 | 20 | private SEAT_TYPE seatType; 21 | 22 | /** 23 | * @param commandId 24 | * @param aggregateId 25 | */ 26 | public TicketProtectCommand(long commandId, long aggregateId) { 27 | super(commandId, aggregateId); 28 | } 29 | 30 | public Integer getStartStationNumber() { 31 | return startStationNumber; 32 | } 33 | 34 | public void setStartStationNumber(Integer startStationNumber) { 35 | this.startStationNumber = startStationNumber; 36 | } 37 | 38 | public Integer getEndStationNumber() { 39 | return endStationNumber; 40 | } 41 | 42 | public void setEndStationNumber(Integer endStationNumber) { 43 | this.endStationNumber = endStationNumber; 44 | } 45 | 46 | public Integer getMinCanBuyTicketCount() { 47 | return minCanBuyTicketCount; 48 | } 49 | 50 | public void setMinCanBuyTicketCount(Integer minCanBuyTicketCount) { 51 | this.minCanBuyTicketCount = minCanBuyTicketCount; 52 | } 53 | 54 | public Integer getMaxCanBuyTicketCount() { 55 | return maxCanBuyTicketCount; 56 | } 57 | 58 | public void setMaxCanBuyTicketCount(Integer maxCanBuyTicketCount) { 59 | this.maxCanBuyTicketCount = maxCanBuyTicketCount; 60 | } 61 | 62 | public Boolean getStrict() { 63 | return strict; 64 | } 65 | 66 | public void setStrict(Boolean strict) { 67 | this.strict = strict; 68 | } 69 | 70 | public SEAT_TYPE getSeatType() { 71 | return seatType; 72 | } 73 | 74 | public void setSeatType(SEAT_TYPE seatType) { 75 | this.seatType = seatType; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /cqrs-message-kafka/src/main/java/com/damon/cqrs/kafka/config/KafkaConsumerConfig.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.kafka.config; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Properties; 6 | 7 | @Data 8 | public class KafkaConsumerConfig { 9 | private String groupId; 10 | private String bootstrapServers; 11 | private String topic; 12 | private Properties properties; 13 | 14 | public KafkaConsumerConfig(String bootstrapServers, String topic, String groupId) { 15 | Properties properties = new Properties(); 16 | properties.put("bootstrap.servers", bootstrapServers); 17 | properties.put("group.id", groupId); 18 | properties.put("enable.auto.commit", "false"); 19 | properties.put("max.poll.records", 1024); 20 | /** 21 | * max.poll.interval.ms默认值是5分钟,如果需要加大时长就需要给这个参数重新赋值 22 | * 23 | * 这里解释下自己为什么要修改这个参数:因为第一次接收kafka数据,需要加载一堆基础数据,大概执行时间要8分钟, 24 | * 而5分钟后,kafka认为我没消费,又重新发送,导致我这边收到许多重复数据,所以我需要调大这个值,避免接收重复数据 25 | */ 26 | properties.put("max.poll.interval.ms", "1800000"); 27 | /** 28 | * 默认值是 10000 毫秒(即 10 秒)。 29 | * session.timeout.ms 必须大于 heartbeat.interval.ms。heartbeat.interval.ms 是消费者向 Kafka 发送心跳的频率, 30 | * 而 session.timeout.ms 是 Kafka 认为消费者失效的最大等待时间。如果心跳间隔小于会话超时时间,则可能会频繁触发再均衡。 31 | * 通常,heartbeat.interval.ms 是 session.timeout.ms 的 1/3 或更小。 32 | * 33 | * 如果消费者处理消息的时间较长,可能需要适当增加 session.timeout.ms,以避免 Kafka 认为消费者失效。 34 | * 例如,如果消费者每次处理消息需要 5 秒钟,而 session.timeout.ms 设置为 3 秒,可能会导致消费者被频繁移除。 35 | */ 36 | properties.put("session.timeout.ms", "60000"); 37 | properties.put("heartbeat.interval.ms", "10000"); 38 | properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); 39 | properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); 40 | /** 41 | * 如果存在已经提交的offest时,不管设置为earliest 或者latest 都会从已经提交的offest处开始消费 42 | * 如果不存在已经提交的offest时,earliest 表示从头开始消费,latest 表示从最新的数据消费,也就是新产生的数据. 43 | * none topic各分区都存在已提交的offset时,从提交的offest处开始消费;只要有一个分区不存在已提交的offset,则抛出异常 44 | */ 45 | properties.put("auto.offset.reset", "earliest"); 46 | this.properties = properties; 47 | this.topic = topic; 48 | this.groupId = groupId; 49 | this.bootstrapServers = bootstrapServers; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cqrs-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.damon.cqrs 8 | cqrs 9 | ${revision} 10 | 11 | cqrs-core 12 | cqrs-core 13 | 14 | UTF-8 15 | 16 | 17 | 18 | com.damon.cqrs 19 | cqrs-api 20 | 21 | 22 | org.projectlombok 23 | lombok 24 | 25 | 26 | com.google.guava 27 | guava 28 | 29 | 30 | ch.qos.logback 31 | logback-access 32 | 33 | 34 | ch.qos.logback 35 | logback-classic 36 | 37 | 38 | ch.qos.logback 39 | logback-core 40 | 41 | 42 | com.alibaba 43 | fastjson 44 | 45 | 46 | com.github.ben-manes.caffeine 47 | caffeine 48 | 49 | 50 | cglib 51 | cglib 52 | 53 | 54 | cn.hutool 55 | hutool-all 56 | 57 | 58 | com.lmax 59 | disruptor 60 | 3.4.4 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /cqrs-sample/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.damon.cqrs 7 | cqrs 8 | ${revision} 9 | 10 | cqrs-sample 11 | cqrs-sample 12 | pom 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | 20 | com.zaxxer 21 | HikariCP 22 | 3.4.3 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter 27 | 2.7.11 28 | 29 | 30 | com.damon.cqrs 31 | cqrs-message-rocketmq 32 | ${revision} 33 | 34 | 35 | com.damon.cqrs 36 | cqrs-event-mysql 37 | ${revision} 38 | 39 | 40 | cglib 41 | cglib 42 | 3.3.0 43 | 44 | 45 | 46 | 47 | cqrs-sample-generic-test 48 | 49 | 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-compiler-plugin 54 | 3.1 55 | 56 | ${java.version} 57 | ${java.version} 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/red_packet/domain/service/RedPacketCommandService.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.red_packet.domain.service; 2 | 3 | import com.damon.cqrs.command.CommandService; 4 | import com.damon.cqrs.config.CqrsConfig; 5 | import com.damon.cqrs.sample.red_packet.api.IRedPacketCommandService; 6 | import com.damon.cqrs.sample.red_packet.api.command.RedPacketCreateCommand; 7 | import com.damon.cqrs.sample.red_packet.api.command.RedPacketGetCommand; 8 | import com.damon.cqrs.sample.red_packet.api.command.RedPacketGrabCommand; 9 | import com.damon.cqrs.sample.red_packet.api.dto.WeixinRedPacketDTO; 10 | import com.damon.cqrs.sample.red_packet.domain.aggregate.WeixinRedPacket; 11 | 12 | import java.util.concurrent.CompletableFuture; 13 | 14 | /** 15 | * @author xianpinglu 16 | */ 17 | public class RedPacketCommandService extends CommandService implements IRedPacketCommandService { 18 | 19 | public RedPacketCommandService(CqrsConfig cqrsConfig) { 20 | super(cqrsConfig); 21 | } 22 | 23 | @Override 24 | public void createRedPackage(final RedPacketCreateCommand command) { 25 | super.process(command, () -> new WeixinRedPacket(command)).join(); 26 | } 27 | 28 | @Override 29 | public int grabRedPackage(final RedPacketGrabCommand command) { 30 | return super.process(command, redPacket -> redPacket.grabRedPackage(command)).join(); 31 | } 32 | 33 | @Override 34 | public WeixinRedPacketDTO get(final RedPacketGetCommand command) { 35 | CompletableFuture future = super.process( 36 | command, 37 | redPacket -> { 38 | WeixinRedPacketDTO redPacketDTO = new WeixinRedPacketDTO(); 39 | redPacketDTO.setMap(redPacket.getMap()); 40 | redPacketDTO.setId(redPacket.getId()); 41 | redPacketDTO.setRedpacketStack(redPacket.getRedpacketStack()); 42 | redPacketDTO.setSponsorId(redPacket.getSponsorId()); 43 | return redPacketDTO; 44 | } 45 | ); 46 | return future.join(); 47 | } 48 | 49 | 50 | @Override 51 | public CompletableFuture saveAggregateSnapshot(WeixinRedPacket aggregate) { 52 | return super.saveAggregateSnapshot(aggregate); 53 | } 54 | 55 | @Override 56 | public WeixinRedPacket createAggregateSnapshot(WeixinRedPacket aggregate) { 57 | return super.createAggregateSnapshot(aggregate); 58 | } 59 | 60 | @Override 61 | public long snapshotCycle() { 62 | return super.snapshotCycle(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /cqrs-event-mysql/src/main/java/com/damon/cqrs/event_store/MysqlEventOffset.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.event_store; 2 | 3 | import com.damon.cqrs.exception.EventOfffsetUpdateException; 4 | import com.damon.cqrs.exception.EventQueryException; 5 | import com.damon.cqrs.store.IEventOffset; 6 | import org.apache.commons.dbutils.QueryRunner; 7 | import org.apache.commons.dbutils.handlers.MapListHandler; 8 | 9 | import javax.sql.DataSource; 10 | import java.sql.SQLException; 11 | import java.util.*; 12 | 13 | /** 14 | * 事件偏移位置存储 15 | *

16 | * 用于记录event store已发送的事件的偏移位置 17 | * 18 | * @author xianping_lu 19 | */ 20 | public class MysqlEventOffset implements IEventOffset { 21 | 22 | private final String QUERY_EVENT_OFFSET = "SELECT id, event_offset_id, data_source_name, table_name FROM event_offset"; 23 | 24 | private final String UPDATE_EVENT_OFFSET = "UPDATE event_offset SET event_offset_id = ? where id = ? "; 25 | 26 | private final Map dataSourceNameMap; 27 | 28 | public MysqlEventOffset(final List dataSourceMappings) { 29 | dataSourceNameMap = new HashMap<>(); 30 | dataSourceMappings.forEach(mapping -> { 31 | dataSourceNameMap.put(mapping.getDataSourceName(), mapping.getDataSource()); 32 | }); 33 | } 34 | 35 | @Override 36 | public List> queryEventOffset() { 37 | List> list = new ArrayList<>(); 38 | Set set = dataSourceNameMap.keySet(); 39 | for (String name : set) { 40 | DataSource dataSource = dataSourceNameMap.get(name); 41 | QueryRunner queryRunner = new QueryRunner(dataSource); 42 | List> rows = null; 43 | try { 44 | rows = queryRunner.query(QUERY_EVENT_OFFSET, new MapListHandler()); 45 | } catch (SQLException e) { 46 | throw new EventQueryException(e); 47 | } 48 | list.addAll(rows); 49 | } 50 | return list; 51 | 52 | } 53 | 54 | @Override 55 | public void updateEventOffset(String dataSourceName, long offsetId, long id) { 56 | DataSource dataSource = dataSourceNameMap.get(dataSourceName); 57 | QueryRunner queryRunner = new QueryRunner(dataSource); 58 | int result = 0; 59 | try { 60 | result = queryRunner.update(UPDATE_EVENT_OFFSET, offsetId, id); 61 | } catch (SQLException e) { 62 | throw new EventOfffsetUpdateException(e); 63 | } 64 | if (result == 0) { 65 | throw new EventOfffsetUpdateException("update event offset failed"); 66 | } 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/goods/domain/handler/GoodsCommandService.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.goods.domain.handler; 2 | 3 | 4 | import com.damon.cqrs.command.CommandService; 5 | import com.damon.cqrs.config.CqrsConfig; 6 | import com.damon.cqrs.sample.goods.api.GoodsCreateCommand; 7 | import com.damon.cqrs.sample.goods.api.GoodsStockCancelDeductionCommand; 8 | import com.damon.cqrs.sample.goods.api.GoodsStockCommitDeductionCommand; 9 | import com.damon.cqrs.sample.goods.api.GoodsStockTryDeductionCommand; 10 | import com.damon.cqrs.sample.goods.domain.aggregate.Goods; 11 | 12 | import java.util.concurrent.CompletableFuture; 13 | 14 | public class GoodsCommandService extends CommandService implements IGoodsCommandService { 15 | 16 | public GoodsCommandService(CqrsConfig cqrsConfig) { 17 | super(cqrsConfig); 18 | } 19 | 20 | @Override 21 | public CompletableFuture createGoodsStock(GoodsCreateCommand command) { 22 | return super.process(command, () -> new Goods(command.getAggregateId(), command.getName(), command.getNumber())); 23 | } 24 | 25 | @Override 26 | public CompletableFuture tryDeductionStock(GoodsStockTryDeductionCommand command) { 27 | return super.process(command, goods -> goods.tryDeductionStock(command.getOrderId(), command.getNumber())); 28 | } 29 | 30 | @Override 31 | public CompletableFuture commitDeductionStock(GoodsStockCommitDeductionCommand command) { 32 | return super.process(command, goods -> goods.commitDeductionStock(command.getOrderId())); 33 | } 34 | 35 | @Override 36 | public CompletableFuture cancelDeductionStock(GoodsStockCancelDeductionCommand command) { 37 | return super.process(command, goods -> goods.cancelDeductionStock(command.getOrderId())); 38 | } 39 | 40 | 41 | @Override 42 | public Goods getAggregateSnapshot(long aggregateId, Class classes) { 43 | return null; 44 | } 45 | 46 | @Override 47 | public CompletableFuture saveAggregateSnapshot(Goods goods) { 48 | System.out.println(goods.getId() + ":" + goods.getNumber() + ":" + goods.getName() + ":" + goods.getVersion()); 49 | return CompletableFuture.completedFuture(true); 50 | } 51 | 52 | // @Override 53 | // public Goods createAggregateSnapshot(Goods aggregate) { 54 | // Goods snap = new Goods(); 55 | // snap.setName(aggregate.getName()); 56 | // snap.setNumber(aggregate.getNumber()); 57 | // snap.setId(aggregate.getId()); 58 | // snap.setVersion(aggregate.getVersion()); 59 | // return snap; 60 | // } 61 | // 62 | // @Override 63 | // public long snapshotCycle() { 64 | // return 5; 65 | // } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/config/CqrsConfig.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.config; 2 | 3 | import cn.hutool.core.util.ClassUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import com.damon.cqrs.cache.IAggregateCache; 6 | import com.damon.cqrs.domain.Event; 7 | import com.damon.cqrs.event.EventCommittingService; 8 | import com.damon.cqrs.snapshot.IAggregateSnapshootService; 9 | import com.damon.cqrs.store.IEventStore; 10 | import lombok.Builder; 11 | import lombok.Data; 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | import java.lang.reflect.Field; 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.List; 18 | import java.util.Set; 19 | import java.util.stream.Collectors; 20 | 21 | @Builder 22 | @Data 23 | @Slf4j 24 | public class CqrsConfig { 25 | private final static String SVUID = "serialVersionUID"; 26 | 27 | static { 28 | // 检测event是否拥有无参构造方法、属性是否具备set get方法 29 | eventEntityStandardInspect(); 30 | } 31 | 32 | private IAggregateCache aggregateCache; 33 | private IEventStore eventStore; 34 | private IAggregateSnapshootService aggregateSnapshootService; 35 | private EventCommittingService eventCommittingService; 36 | private AggregateSlotLock aggregateSlotLock; 37 | 38 | private static void eventEntityStandardInspect() { 39 | Set> classSet = ClassUtil.scanPackageBySuper(StrUtil.EMPTY, Event.class); 40 | List errors = new ArrayList<>(); 41 | for (Class cla : classSet) { 42 | try { 43 | cla.getDeclaredConstructor(); 44 | } catch (NoSuchMethodException e) { 45 | String message = String.format("class: %s, need to provide a parameterless constructor.\n", cla.getTypeName()); 46 | errors.add(message); 47 | } 48 | Field[] fields = cla.getDeclaredFields(); 49 | Set methodNames = Arrays.stream(cla.getMethods()).map(method -> method.getName().toUpperCase()).collect(Collectors.toSet()); 50 | for (Field field : fields) { 51 | if (!isValidField(field.getName(), methodNames)) { 52 | String message = String.format("class: %s, field : %s, missing get set method.\n", cla.getTypeName(), field.getName()); 53 | errors.add(message); 54 | } 55 | } 56 | } 57 | if (!errors.isEmpty()) { 58 | throw new IllegalArgumentException(errors.toString()); 59 | } 60 | } 61 | 62 | private static boolean isValidField(String fieldName, Set methodNames) { 63 | if (fieldName.equals(SVUID)) { 64 | return true; 65 | } 66 | return methodNames.contains("GET" + fieldName.toUpperCase()) && methodNames.contains("SET" + fieldName.toUpperCase()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /cqrs-core/src/main/java/com/damon/cqrs/utils/ConsistentHashShard.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.utils; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.nio.ByteOrder; 5 | import java.util.List; 6 | import java.util.SortedMap; 7 | import java.util.TreeMap; 8 | 9 | /** 10 | * 用TreeMap做虚拟节点环的一致性哈希算法,
11 | * 采用自 https://blog.csdn.net/mae4a8cs/article/details/55000865
12 | * 这篇文章也是转载的,如果你知道,欢迎告诉我原文地址 13 | * 14 | * @param 15 | * @author kimffy 16 | */ 17 | public class ConsistentHashShard { 18 | 19 | private final int NODE_NUM = 64; // 每个机器节点关联的虚拟节点个数 20 | private final List shards; // 真实机器节点 21 | private TreeMap nodes; // 虚拟节点 22 | 23 | public ConsistentHashShard(List shards) { 24 | super(); 25 | this.shards = shards; 26 | init(); 27 | } 28 | 29 | private void init() { // 初始化一致性hash环 30 | nodes = new TreeMap(); 31 | for (int i = 0; i != shards.size(); ++i) { // 每个真实机器节点都需要关联虚拟节点 32 | final S shardInfo = shards.get(i); 33 | 34 | for (int n = 0; n < NODE_NUM; n++) 35 | // 一个真实机器节点关联NODE_NUM个虚拟节点 36 | nodes.put(hash("SHARD-" + i + "-NODE-" + n), shardInfo); 37 | 38 | } 39 | } 40 | 41 | public S getShardInfo(String key) { 42 | SortedMap tail = nodes.tailMap(hash(key)); // 沿环的顺时针找到一个虚拟节点 43 | if (tail.isEmpty()) { 44 | return nodes.get(nodes.firstKey()); 45 | } 46 | return tail.get(tail.firstKey()); // 返回该虚拟节点对应的真实机器节点的信息 47 | } 48 | 49 | /** 50 | * MurMurHash算法,是非加密HASH算法,性能很高, 51 | * 比传统的CRC32,MD5,SHA-1(这两个算法都是加密HASH算法,复杂度本身就很高,带来的性能上的损害也不可避免) 52 | * 等HASH算法要快很多,而且据说这个算法的碰撞率很低. http://murmurhash.googlepages.com/ 53 | */ 54 | private Long hash(String key) { 55 | 56 | ByteBuffer buf = ByteBuffer.wrap(key.getBytes()); 57 | int seed = 0x1234ABCD; 58 | 59 | ByteOrder byteOrder = buf.order(); 60 | buf.order(ByteOrder.LITTLE_ENDIAN); 61 | 62 | long m = 0xc6a4a7935bd1e995L; 63 | int r = 47; 64 | 65 | long h = seed ^ (buf.remaining() * m); 66 | 67 | long k; 68 | while (buf.remaining() >= 8) { 69 | k = buf.getLong(); 70 | 71 | k *= m; 72 | k ^= k >>> r; 73 | k *= m; 74 | 75 | h ^= k; 76 | h *= m; 77 | } 78 | 79 | if (buf.remaining() > 0) { 80 | ByteBuffer finish = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); 81 | // for big-endian version, do this first: 82 | // finish.position(8-buf.remaining()); 83 | finish.put(buf).rewind(); 84 | h ^= finish.getLong(); 85 | h *= m; 86 | } 87 | 88 | h ^= h >>> r; 89 | h *= m; 90 | h ^= h >>> r; 91 | 92 | buf.order(byteOrder); 93 | return h; 94 | } 95 | 96 | } -------------------------------------------------------------------------------- /cqrs-message-rocketmq/src/main/java/com/damon/cqrs/rocketmq/RocketMQSendService.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.rocketmq; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.damon.cqrs.event.EventSendingContext; 5 | import com.damon.cqrs.event.ISendMessageService; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.rocketmq.client.exception.MQBrokerException; 8 | import org.apache.rocketmq.client.exception.MQClientException; 9 | import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; 10 | import org.apache.rocketmq.client.producer.SendResult; 11 | import org.apache.rocketmq.client.producer.SendStatus; 12 | import org.apache.rocketmq.common.message.Message; 13 | import org.apache.rocketmq.common.message.MessageQueue; 14 | import org.apache.rocketmq.remoting.exception.RemotingException; 15 | 16 | import java.nio.charset.StandardCharsets; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.stream.Collectors; 20 | 21 | /** 22 | * @author xianping_lu 23 | */ 24 | @Slf4j 25 | public class RocketMQSendService implements ISendMessageService { 26 | 27 | private final DefaultMQProducer producer; 28 | 29 | private final String topic; 30 | 31 | private final long timeout; 32 | 33 | public RocketMQSendService(DefaultMQProducer producer, String topic, long timeout) { 34 | this.producer = producer; 35 | this.topic = topic; 36 | this.timeout = timeout; 37 | } 38 | 39 | @Override 40 | public void sendMessage(List eventSendingContexts) { 41 | 42 | TopicPublishInfo topicPublishInfo = producer.tryToFindTopicPublishInfo(topic); 43 | List queues = topicPublishInfo.getMessageQueueList(); 44 | Map> map = eventSendingContexts.stream().collect(Collectors.groupingBy(context -> { 45 | int hashCode = context.getAggregateId().hashCode(); 46 | hashCode = hashCode < 0 ? Math.abs(hashCode) : hashCode; 47 | return hashCode % queues.size(); 48 | })); 49 | 50 | map.keySet().parallelStream().forEach((index) -> { 51 | List contexts = map.get(index); 52 | List msgs = contexts.stream().map(event -> 53 | new Message(topic, JSONObject.toJSONString(event.getEvents()).getBytes(StandardCharsets.UTF_8))).collect(Collectors.toList() 54 | ); 55 | MessageQueue queue = queues.get(index); 56 | SendResult result; 57 | try { 58 | result = producer.send(msgs, queue, timeout); 59 | } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) { 60 | log.error("event sending failed.", e); 61 | return; 62 | } 63 | if (result.getSendStatus().equals(SendStatus.SEND_OK)) { 64 | log.info("batch sending event size :{} succeed.", msgs.size()); 65 | } else { 66 | throw new RuntimeException("rocketmq event store failed, status : " + result.getSendStatus()); 67 | } 68 | 69 | }); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/red_packet/RedPacketServiceVirtualThreadBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.red_packet; 2 | 3 | import com.damon.cqrs.config.CqrsConfig; 4 | import com.damon.cqrs.sample.TestConfig; 5 | import com.damon.cqrs.sample.red_packet.api.command.RedPacketCreateCommand; 6 | import com.damon.cqrs.sample.red_packet.api.command.RedPacketGrabCommand; 7 | import com.damon.cqrs.sample.red_packet.domain.service.RedPacketCommandService; 8 | import com.damon.cqrs.utils.IdWorker; 9 | 10 | import java.math.BigDecimal; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Random; 14 | import java.util.concurrent.CountDownLatch; 15 | import java.util.concurrent.ExecutorService; 16 | import java.util.concurrent.Executors; 17 | 18 | 19 | public class RedPacketServiceVirtualThreadBootstrap { 20 | 21 | public static void main(String[] args) throws InterruptedException { 22 | CqrsConfig cqrsConfig = TestConfig.init(); 23 | RedPacketCommandService redPacketServcie = new RedPacketCommandService(cqrsConfig); 24 | List ids = new ArrayList<>(); 25 | for (int i = 1; i <= 1000; i++) { 26 | Long id = IdWorker.getId(); 27 | RedPacketCreateCommand create = new RedPacketCreateCommand(IdWorker.getId(), id); 28 | create.setMoney(new BigDecimal(20000)); 29 | create.setNumber(new BigDecimal(10000)); 30 | create.setMinMoney(new BigDecimal(1)); 31 | create.setSponsorId(1L); 32 | redPacketServcie.createRedPackage(create); 33 | ids.add(id); 34 | } 35 | Random random = new Random(); 36 | CountDownLatch latch = new CountDownLatch(4 * 2000 * 1000); 37 | int size = ids.size(); 38 | ExecutorService service = Executors.newVirtualThreadPerTaskExecutor(); 39 | Long startDate = System.currentTimeMillis(); 40 | System.out.println("start"); 41 | for (int i = 0; i < 8000; i++) { 42 | service.submit(() -> { 43 | for (int number = 0; number < 200000; number++) { 44 | try { 45 | int index = random.nextInt(size); 46 | Long commandId = IdWorker.getId(); 47 | Long id = ids.get(index); 48 | RedPacketGrabCommand grabCommand = new RedPacketGrabCommand(commandId, id); 49 | grabCommand.setUserId(IdWorker.getId()); 50 | int status = redPacketServcie.grabRedPackage(grabCommand); 51 | if (status <= 0) { 52 | System.out.println("failed"); 53 | } 54 | } catch (Exception e) { 55 | e.printStackTrace(); 56 | } finally { 57 | latch.countDown(); 58 | } 59 | } 60 | }); 61 | } 62 | latch.await(); 63 | Long endDate = System.currentTimeMillis(); 64 | System.out.println(endDate - startDate); 65 | 66 | } 67 | 68 | } 69 | 70 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/goods/GoodsApplication.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.goods; 2 | 3 | import com.damon.cqrs.config.CqrsConfig; 4 | import com.damon.cqrs.sample.TestConfig; 5 | import com.damon.cqrs.sample.goods.api.GoodsCreateCommand; 6 | import com.damon.cqrs.sample.goods.api.GoodsStockTryDeductionCommand; 7 | import com.damon.cqrs.sample.goods.domain.aggregate.Goods; 8 | import com.damon.cqrs.sample.goods.domain.handler.GoodsCommandService; 9 | import com.damon.cqrs.utils.IdWorker; 10 | 11 | import java.util.*; 12 | import java.util.concurrent.*; 13 | 14 | public class GoodsApplication { 15 | 16 | private static final int runTotalCount = 4 * 2000 * 1000; 17 | 18 | private static final int goodsCount = 4; 19 | 20 | private static final int threadNumber = 400; 21 | 22 | private static final ExecutorService service = Executors.newFixedThreadPool(threadNumber); 23 | 24 | private static final int exeCount = 100000; 25 | 26 | public static void main(String[] args) throws Exception { 27 | CqrsConfig cqrsConfig = TestConfig.init(); 28 | GoodsCommandService handler = new GoodsCommandService(cqrsConfig); 29 | List goodsIds = initGoods(handler); 30 | int size = goodsIds.size(); 31 | CountDownLatch latch = new CountDownLatch(runTotalCount); 32 | long from = new Date().getTime(); 33 | System.out.println("start"); 34 | for (int i = 0; i < threadNumber; i++) { 35 | service.submit(() -> { 36 | for (int count = 0; count < exeCount; count++) { 37 | int index = ThreadLocalRandom.current().nextInt(size); 38 | CompletableFuture future = handler.tryDeductionStock(new GoodsStockTryDeductionCommand(IdWorker.getId(), goodsIds.get(index))); 39 | try { 40 | future.join(); 41 | } catch (Exception e) { 42 | e.printStackTrace(); 43 | } finally { 44 | latch.countDown(); 45 | } 46 | } 47 | }); 48 | } 49 | latch.await(); 50 | long time = calculateTimeConsumption(from, new Date().getTime()); 51 | long tps = runTotalCount / (time / 1000); 52 | System.out.println("tps:" + tps); 53 | } 54 | 55 | private static List initGoods(GoodsCommandService handler) { 56 | List ids = new ArrayList<>(); 57 | for (int i = 1; i <= goodsCount; i++) { 58 | Map shardingParms = new HashMap<>(); 59 | shardingParms.put("a1", "a" + i); 60 | GoodsCreateCommand command1 = new GoodsCreateCommand(IdWorker.getId(), i, "iphone 6 plus " + i, 1000); 61 | System.out.println(handler.process(command1, () -> new Goods(command1.getAggregateId(), command1.getName(), command1.getNumber())).join()); 62 | ids.add((long) (i)); 63 | } 64 | return ids; 65 | } 66 | 67 | private static long calculateTimeConsumption(long from, long to) { 68 | return to - from; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/red_packet/RedPacketServiceBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.red_packet; 2 | 3 | import com.damon.cqrs.config.CqrsConfig; 4 | import com.damon.cqrs.sample.TestConfig; 5 | import com.damon.cqrs.sample.red_packet.api.command.RedPacketCreateCommand; 6 | import com.damon.cqrs.sample.red_packet.api.command.RedPacketGrabCommand; 7 | import com.damon.cqrs.sample.red_packet.domain.service.RedPacketCommandService; 8 | import com.damon.cqrs.utils.IdWorker; 9 | import org.apache.rocketmq.client.exception.MQClientException; 10 | 11 | import java.math.BigDecimal; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Random; 15 | import java.util.concurrent.CountDownLatch; 16 | import java.util.concurrent.ExecutorService; 17 | import java.util.concurrent.Executors; 18 | 19 | 20 | public class RedPacketServiceBootstrap { 21 | public static void main(String[] args) throws InterruptedException, MQClientException { 22 | CqrsConfig cqrsConfig = TestConfig.init(); 23 | RedPacketCommandService redPacketServcie = new RedPacketCommandService(cqrsConfig); 24 | List ids = new ArrayList<>(); 25 | for (int i = 1; i <= 2000; i++) { 26 | Long id = IdWorker.getId(); 27 | RedPacketCreateCommand create = new RedPacketCreateCommand(IdWorker.getId(), id); 28 | create.setMoney(new BigDecimal(20000)); 29 | create.setNumber(new BigDecimal(10000)); 30 | create.setMinMoney(new BigDecimal(1)); 31 | create.setSponsorId(1L); 32 | redPacketServcie.createRedPackage(create); 33 | ids.add(id); 34 | } 35 | Random random = new Random(); 36 | CountDownLatch latch = new CountDownLatch(2 * 2000 * 1000); 37 | int size = ids.size(); 38 | ExecutorService service = Executors.newFixedThreadPool(1000); 39 | Long startDate = System.currentTimeMillis(); 40 | System.out.println("start"); 41 | for (int i = 0; i < 500; i++) { 42 | service.submit(() -> { 43 | for (int number = 0; number < 300000; number++) { 44 | try { 45 | int index = random.nextInt(size); 46 | Long commandId = IdWorker.getId(); 47 | Long id = ids.get(index); 48 | RedPacketGrabCommand grabCommand = new RedPacketGrabCommand(commandId, id); 49 | grabCommand.setUserId(IdWorker.getId()); 50 | int status = redPacketServcie.grabRedPackage(grabCommand); 51 | if (status <= 0) { 52 | System.out.println("failed"); 53 | } 54 | } catch (Exception e) { 55 | e.printStackTrace(); 56 | } finally { 57 | latch.countDown(); 58 | } 59 | } 60 | }); 61 | } 62 | latch.await(); 63 | Long endDate = System.currentTimeMillis(); 64 | System.out.println(endDate - startDate); 65 | 66 | } 67 | 68 | } 69 | 70 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/test/java/TestRun.java.bak: -------------------------------------------------------------------------------- 1 | import com.damon.cqrs.sample.red_packet.api.command.RedPacketCreateCommand; 2 | import com.damon.cqrs.sample.red_packet.api.command.RedPacketGrabCommand; 3 | import com.damon.cqrs.sample.red_packet.domain.service.RedPacketCommandService; 4 | import com.damon.cqrs.utils.IdWorker; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | 11 | import java.math.BigDecimal; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Random; 15 | import java.util.concurrent.CountDownLatch; 16 | import java.util.concurrent.ExecutorService; 17 | import java.util.concurrent.Executors; 18 | 19 | @RunWith(SpringRunner.class) 20 | @SpringBootTest(classes = RedPacketServiceVirtualThreadSyncBootstrap.class) 21 | public class TestRun { 22 | @Autowired 23 | private RedPacketCommandService commandService; 24 | 25 | @Test 26 | public void test() throws InterruptedException { 27 | List ids = new ArrayList<>(); 28 | for (int i = 1; i <= 2000; i++) { 29 | Long id = IdWorker.getId(); 30 | RedPacketCreateCommand create = new RedPacketCreateCommand(IdWorker.getId(), id); 31 | create.setMoney(new BigDecimal(20000)); 32 | create.setNumber(new BigDecimal(10000)); 33 | create.setMinMoney(new BigDecimal(1)); 34 | create.setSponsorId(1L); 35 | commandService.createRedPackage(create); 36 | ids.add(id); 37 | } 38 | Random random = new Random(); 39 | CountDownLatch latch = new CountDownLatch(4 * 2000 * 1000); 40 | int size = ids.size(); 41 | ExecutorService service = Executors.newVirtualThreadPerTaskExecutor(); 42 | Long startDate = System.currentTimeMillis(); 43 | System.out.println("start"); 44 | for (int i = 0; i < 4000; i++) { 45 | service.submit(() -> { 46 | for (int number = 0; number < 200000; number++) { 47 | try { 48 | int index = random.nextInt(size); 49 | Long commandId = IdWorker.getId(); 50 | Long id = ids.get(index); 51 | RedPacketGrabCommand grabCommand = new RedPacketGrabCommand(commandId, id); 52 | grabCommand.setUserId(IdWorker.getId()); 53 | int status = commandService.grabRedPackage(grabCommand); 54 | if (status <= 0) { 55 | System.out.println("failed"); 56 | } 57 | } catch (Exception e) { 58 | e.printStackTrace(); 59 | } finally { 60 | latch.countDown(); 61 | } 62 | } 63 | }); 64 | } 65 | latch.await(); 66 | Long endDate = System.currentTimeMillis(); 67 | System.out.println(endDate - startDate); 68 | } 69 | 70 | 71 | } 72 | -------------------------------------------------------------------------------- /cqrs-api/src/main/java/com/damon/cqrs/utils/AggregateConflictRetryUtils.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.utils; 2 | 3 | import com.damon.cqrs.domain.Command; 4 | import com.damon.cqrs.exception.AggregateCommandConflictException; 5 | import com.damon.cqrs.exception.AggregateEventConflictException; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.util.function.Supplier; 9 | 10 | /** 11 | * 聚合更新冲突重试工具类 12 | *

13 | * 当集群扩容时,有可能导致一个聚合根在多个服务器的问题。 14 | *

15 | * 当出现这个情况时,会有可能出现聚合更新version冲突的问题,我们需要捕获AggregateEventConflictException异常,然后在client发起重试解决这个问题。 16 | * 17 | * @author xianping_lu 18 | */ 19 | @Slf4j 20 | public class AggregateConflictRetryUtils { 21 | 22 | public static R invoke(Command command, Supplier supplier) { 23 | return invoke(command, supplier, 1); 24 | } 25 | 26 | public static R invoke(Command command, Supplier supplier, int retryNumber) { 27 | for (int i = 0; i <= retryNumber; i++) { 28 | try { 29 | return supplier.get(); 30 | } catch (Throwable e) { 31 | if (e.getCause() instanceof AggregateEventConflictException) { 32 | AggregateEventConflictException ex = (AggregateEventConflictException) e.getCause(); 33 | log.error("aggregate update conflict, aggregate id : {}, type : {}.", ex.getAggregateId(), ex.getAggregateType(), e); 34 | if (i == retryNumber) { 35 | throw ex; 36 | } 37 | } else if (e instanceof AggregateEventConflictException) { 38 | AggregateEventConflictException ex = (AggregateEventConflictException) e; 39 | log.error("aggregate update conflict, aggregate id : {}, type : {}.", ex.getAggregateId(), ex.getAggregateType(), e); 40 | if (i == retryNumber) { 41 | throw ex; 42 | } 43 | } else if (e.getCause() instanceof AggregateCommandConflictException) { 44 | AggregateCommandConflictException ex = (AggregateCommandConflictException) e.getCause(); 45 | long commandId = ex.getCommandId(); 46 | log.error("aggregate update conflict, aggregate id : {}, type : {}, command id : {}.", ex.getAggregateId(), ex.getAggregateType(), commandId, e); 47 | if (commandId == command.getCommandId()) { 48 | throw ex; 49 | } 50 | } else if (e instanceof AggregateCommandConflictException) { 51 | AggregateCommandConflictException ex = (AggregateCommandConflictException) e; 52 | long commandId = ex.getCommandId(); 53 | log.error("aggregate update conflict, aggregate id : {}, type : {}, command id : {}.", ex.getAggregateId(), ex.getAggregateType(), commandId, e); 54 | if (commandId == command.getCommandId()) { 55 | throw ex; 56 | } 57 | } else { 58 | throw e; 59 | } 60 | } 61 | } 62 | throw new AggregateEventConflictException("aggregate update conflict, retry failed."); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/train/aggregate/value_object/S2SMaxTicketCountProtectInfo.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.train.aggregate.value_object; 2 | 3 | import java.util.BitSet; 4 | import java.util.concurrent.ConcurrentSkipListMap; 5 | 6 | /** 7 | * 站点到站点座位的限制 8 | */ 9 | public class S2SMaxTicketCountProtectInfo { 10 | /** 11 | * 开始站点 12 | */ 13 | private Integer fromStation; 14 | /** 15 | * 结束站点 16 | */ 17 | private Integer toStation; 18 | /** 19 | * 站在间预留的座位索引,从0开始 20 | */ 21 | private BitSet seatIndexBitSet; 22 | /** 23 | * 站点间可以购买座位的保留数量 24 | */ 25 | private Integer protectCanBuySeatCount; 26 | /** 27 | * 站点间可以购买座位的最大数量 28 | */ 29 | private Integer maxCanBuySeatCount; 30 | /** 31 | * 站到站点间预留的座位索引 例如:key:10002 value: 00001111 表示站点1至2预留索引4-7 索引的座位(总计4个座位) 32 | */ 33 | private ConcurrentSkipListMap s2sProtectSeatIndexBitSet; 34 | 35 | public S2SMaxTicketCountProtectInfo(Integer fromStation, 36 | Integer toStation, 37 | Integer protectCanBuySeat, 38 | Integer maxCanBuySeatCount, 39 | BitSet seatIndexBitSet, 40 | ConcurrentSkipListMap s2sSeatIndexBitSet) { 41 | this.fromStation = fromStation; 42 | this.toStation = toStation; 43 | this.maxCanBuySeatCount = maxCanBuySeatCount; 44 | this.protectCanBuySeatCount = protectCanBuySeat; 45 | this.seatIndexBitSet = seatIndexBitSet; 46 | this.s2sProtectSeatIndexBitSet = s2sSeatIndexBitSet; 47 | } 48 | 49 | public ConcurrentSkipListMap getS2sProtectSeatIndexBitSet() { 50 | return s2sProtectSeatIndexBitSet; 51 | } 52 | 53 | public void setS2sProtectSeatIndexBitSet(ConcurrentSkipListMap s2sProtectSeatIndexBitSet) { 54 | this.s2sProtectSeatIndexBitSet = s2sProtectSeatIndexBitSet; 55 | } 56 | 57 | public Integer getMaxCanBuySeatCount() { 58 | return maxCanBuySeatCount; 59 | } 60 | 61 | public void setMaxCanBuySeatCount(Integer maxCanBuySeatCount) { 62 | this.maxCanBuySeatCount = maxCanBuySeatCount; 63 | } 64 | 65 | public Integer getFromStation() { 66 | return fromStation; 67 | } 68 | 69 | public void setFromStation(Integer fromStation) { 70 | this.fromStation = fromStation; 71 | } 72 | 73 | public Integer getToStation() { 74 | return toStation; 75 | } 76 | 77 | public void setToStation(Integer toStation) { 78 | this.toStation = toStation; 79 | } 80 | 81 | public BitSet getSeatIndexBitSet() { 82 | return seatIndexBitSet; 83 | } 84 | 85 | public void setSeatIndexBitSet(BitSet seatIndexBitSet) { 86 | this.seatIndexBitSet = seatIndexBitSet; 87 | } 88 | 89 | public Integer getProtectCanBuySeatCount() { 90 | return protectCanBuySeatCount; 91 | } 92 | 93 | public void setProtectCanBuySeatCount(Integer protectCanBuySeatCount) { 94 | this.protectCanBuySeatCount = protectCanBuySeatCount; 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/goods/domain/aggregate/Goods.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.goods.domain.aggregate; 2 | 3 | import com.damon.cqrs.domain.AggregateRoot; 4 | import com.damon.cqrs.sample.goods.api.GoodsCreatedEvent; 5 | import com.damon.cqrs.sample.goods.api.GoodsStockCancelDeductedEvent; 6 | import com.damon.cqrs.sample.goods.api.GoodsStockCommitDeductedEvent; 7 | import com.damon.cqrs.sample.goods.api.GoodsStockTryDeductedEvent; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public class Goods extends AggregateRoot { 13 | 14 | /** 15 | * 16 | */ 17 | private static final long serialVersionUID = -7591043196387906498L; 18 | 19 | private int number; 20 | 21 | private String name; 22 | 23 | private Map orderStockMap; 24 | 25 | private Long id; 26 | 27 | 28 | public Goods(long id, String name, int number) { 29 | super(id); 30 | applyNewEvent(new GoodsCreatedEvent(id, name, number)); 31 | } 32 | 33 | public int tryDeductionStock(Long orderId, int deductionNumber) { 34 | if (this.number - deductionNumber < 0) { 35 | //库存不足 36 | return -1; 37 | } 38 | applyNewEvent(new GoodsStockTryDeductedEvent(orderId, deductionNumber)); 39 | return 1; 40 | } 41 | 42 | public int commitDeductionStock(Long orderId) { 43 | if (orderStockMap.get(orderId) == null) { 44 | //不存在扣减记录 45 | return -1; 46 | } 47 | applyNewEvent(new GoodsStockCommitDeductedEvent(orderId)); 48 | //成功 49 | return 1; 50 | } 51 | 52 | public int cancelDeductionStock(Long orderId) { 53 | if (orderStockMap.get(orderId) == null) { 54 | //不存在扣减记录 55 | return -1; 56 | } 57 | applyNewEvent(new GoodsStockCancelDeductedEvent(orderId)); 58 | //成功 59 | return 1; 60 | } 61 | 62 | @SuppressWarnings("unused") 63 | private void apply(GoodsStockTryDeductedEvent event) { 64 | number -= event.getNumber(); 65 | orderStockMap.put(event.getOrderId(), event.getNumber()); 66 | } 67 | 68 | private void apply(GoodsStockCommitDeductedEvent event) { 69 | orderStockMap.remove(event.getOrderId()); 70 | } 71 | 72 | private void apply(GoodsStockCancelDeductedEvent event) { 73 | int dedcutedNumber = orderStockMap.get(event.getOrderId()); 74 | this.number += dedcutedNumber; 75 | orderStockMap.remove(event.getOrderId()); 76 | } 77 | 78 | @SuppressWarnings("unused") 79 | private void apply(GoodsCreatedEvent event) { 80 | this.id = event.getId(); 81 | this.name = event.getName(); 82 | this.number = event.getNumber(); 83 | this.orderStockMap = new HashMap<>(); 84 | } 85 | 86 | public int getNumber() { 87 | return number; 88 | } 89 | 90 | public void setNumber(int number) { 91 | this.number = number; 92 | } 93 | 94 | public String getName() { 95 | return name; 96 | } 97 | 98 | public void setName(String name) { 99 | this.name = name; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/metting/MettingTest.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.metting; 2 | 3 | import cn.hutool.core.util.IdUtil; 4 | import com.damon.cqrs.config.CqrsConfig; 5 | import com.damon.cqrs.sample.TestConfig; 6 | import com.damon.cqrs.sample.metting.api.command.MettingCancelCommand; 7 | import com.damon.cqrs.sample.metting.api.command.MettingGetCommand; 8 | import com.damon.cqrs.sample.metting.api.command.MettingReserveCommand; 9 | import com.damon.cqrs.sample.metting.domain.MettingCommandService; 10 | import com.damon.cqrs.sample.metting.domain.aggregate.MeetingId; 11 | import com.damon.cqrs.sample.metting.domain.aggregate.MettingTime; 12 | import com.damon.cqrs.sample.metting.domain.aggregate.ReseveStatus; 13 | 14 | public class MettingTest { 15 | 16 | public static void main(String[] args) throws NoSuchMethodException { 17 | CqrsConfig cqrsConfig = TestConfig.init(); 18 | MettingCommandService commandService = new MettingCommandService(cqrsConfig); 19 | Long userId = 181987L; 20 | String meetingDate = "20230320"; 21 | String mettingNumber = "1103"; 22 | 23 | MeetingId meetingId = new MeetingId(meetingDate, "1103"); 24 | MettingReserveCommand reserveCommand = new MettingReserveCommand( 25 | //预定 0点到 10点会议(大等于0小于9:59) 26 | IdUtil.getSnowflakeNextId(), meetingId.getId(), userId, 27 | new MettingTime(0 * 60, 10 * 60), "UC权限接入议题", 28 | meetingDate, 29 | mettingNumber, 30 | "UC权限接入议题", 31 | "http://xxxxxxxx.xlsx" 32 | ); 33 | //预定1103会议室 34 | ReseveStatus reseveStatus = commandService.reserve(reserveCommand); 35 | //获取1103会议室预定情况 36 | System.out.println(commandService.get(new MettingGetCommand(IdUtil.getSnowflakeNextId(), meetingId.getId()))); 37 | //再次预定1103会议, 已预定无法再次预定 38 | System.out.println(commandService.reserve(reserveCommand).getReserveStatusEnum()); 39 | //取消预定1103,不存在的预定标识无法取消 40 | System.out.println(commandService.cancel(new MettingCancelCommand( 41 | IdUtil.getSnowflakeNextId(), meetingId.getId(), reseveStatus.getReserveFlag() + "3", userId 42 | ))); 43 | //取消预定1103,成功 44 | System.out.println(commandService.cancel(new MettingCancelCommand( 45 | IdUtil.getSnowflakeNextId(), meetingId.getId(), reseveStatus.getReserveFlag(), userId 46 | ))); 47 | //获取1103会议室预定情况 48 | System.out.println(commandService.get(new MettingGetCommand(IdUtil.getSnowflakeNextId(), meetingId.getId()))); 49 | 50 | MettingReserveCommand reserveCommand2 = new MettingReserveCommand( 51 | //预定 10点到 24点会议(大等于10小于23:59) 52 | IdUtil.getSnowflakeNextId(), meetingId.getId(), userId, 53 | new MettingTime(10 * 60, 24 * 60), "RBAC权限接入议题", 54 | meetingDate, 55 | mettingNumber, 56 | "RBAC权限接入议题", 57 | "http://xxxxxxxx.xlsx" 58 | ); 59 | //预定1103会议室 60 | ReseveStatus reseveStatus2 = commandService.reserve(reserveCommand2); 61 | System.out.println(reseveStatus2.getReserveStatusEnum()); 62 | //获取1103会议室预定情况 63 | System.out.println(commandService.get(new MettingGetCommand(IdUtil.getSnowflakeNextId(), meetingId.getId()))); 64 | 65 | 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/goods/GoodsVirtualThreadApplication.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.goods; 2 | 3 | import com.damon.cqrs.config.CqrsConfig; 4 | import com.damon.cqrs.sample.TestConfig; 5 | import com.damon.cqrs.sample.goods.api.GoodsCreateCommand; 6 | import com.damon.cqrs.sample.goods.api.GoodsStockTryDeductionCommand; 7 | import com.damon.cqrs.sample.goods.domain.handler.GoodsCommandService; 8 | import com.damon.cqrs.sample.goods.domain.handler.IGoodsCommandService; 9 | import com.damon.cqrs.utils.IdWorker; 10 | 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.concurrent.*; 16 | 17 | /** 18 | * 基于jdk 19 虚拟线程测试 19 | * 20 | * @author xianpinglu 21 | */ 22 | public class GoodsVirtualThreadApplication { 23 | private static final int goodsCount = 1000; 24 | 25 | private static final int threadNumber = 4000; 26 | 27 | @SuppressWarnings("preview") 28 | private static final ExecutorService service = Executors.newVirtualThreadPerTaskExecutor(); 29 | 30 | private static final int exeCount = 2000; 31 | private static final int runTotalCount = threadNumber * exeCount; 32 | 33 | public static void main(String[] args) throws Exception { 34 | CqrsConfig cqrsConfig = TestConfig.init(); 35 | IGoodsCommandService handler = new GoodsCommandService(cqrsConfig); 36 | List goodsIds = initGoods(handler); 37 | int size = goodsIds.size(); 38 | CountDownLatch latch = new CountDownLatch(runTotalCount); 39 | long from = System.currentTimeMillis(); 40 | for (int i = 0; i < threadNumber; i++) { 41 | service.submit(() -> { 42 | for (int count = 0; count < exeCount; count++) { 43 | int index = ThreadLocalRandom.current().nextInt(size); 44 | GoodsStockTryDeductionCommand cmd = new GoodsStockTryDeductionCommand(IdWorker.getId(), goodsIds.get(index)); 45 | cmd.setNumber(1); 46 | cmd.setOrderId(IdWorker.getId()); 47 | CompletableFuture future = handler.tryDeductionStock(cmd); 48 | try { 49 | future.join(); 50 | } catch (Exception e) { 51 | e.printStackTrace(); 52 | } finally { 53 | latch.countDown(); 54 | } 55 | } 56 | }); 57 | } 58 | latch.await(); 59 | long tps = runTotalCount / (from - System.currentTimeMillis() / 1000); 60 | System.out.println("tps:" + tps); 61 | } 62 | 63 | private static List initGoods(IGoodsCommandService handler) { 64 | List ids = new ArrayList<>(); 65 | for (int i = 1; i <= goodsCount; i++) { 66 | Map shardingParms = new HashMap<>(); 67 | shardingParms.put("a1", "a" + i); 68 | GoodsCreateCommand command1 = new GoodsCreateCommand(IdWorker.getId(), i, "iphone " + i, 1000000000); 69 | handler.createGoodsStock(command1).join(); 70 | ids.add((long) (i)); 71 | } 72 | System.out.println("初始化商品成功, 个数:" + goodsCount); 73 | return ids; 74 | } 75 | 76 | private static long calculateTimeConsumption(long from, long to) { 77 | return to - from; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /cqrs-sample/cqrs-sample-generic-test/src/main/java/com/damon/cqrs/sample/red_packet/RedPacketConfig.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.sample.red_packet; 2 | 3 | import com.damon.cqrs.cache.DefaultAggregateCaffeineCache; 4 | import com.damon.cqrs.cache.IAggregateCache; 5 | import com.damon.cqrs.config.AggregateSlotLock; 6 | import com.damon.cqrs.config.CqrsConfig; 7 | import com.damon.cqrs.event.EventCommittingService; 8 | import com.damon.cqrs.event_store.DataSourceMapping; 9 | import com.damon.cqrs.event_store.DefaultEventShardingRouting; 10 | import com.damon.cqrs.event_store.MysqlEventStore; 11 | import com.damon.cqrs.recovery.AggregateRecoveryService; 12 | import com.damon.cqrs.sample.red_packet.domain.service.RedPacketCommandService; 13 | import com.damon.cqrs.snapshot.DefaultAggregateSnapshootService; 14 | import com.damon.cqrs.snapshot.IAggregateSnapshootService; 15 | import com.damon.cqrs.store.IEventStore; 16 | import com.google.common.collect.Lists; 17 | import com.zaxxer.hikari.HikariDataSource; 18 | import org.springframework.context.annotation.Bean; 19 | import org.springframework.context.annotation.Configuration; 20 | import org.springframework.jdbc.core.JdbcTemplate; 21 | 22 | import javax.sql.DataSource; 23 | import java.util.List; 24 | 25 | @Configuration 26 | public class RedPacketConfig { 27 | @Bean 28 | public HikariDataSource dataSource() { 29 | HikariDataSource dataSource = new HikariDataSource(); 30 | dataSource.setJdbcUrl("jdbc:mysql://localhost:3307/cqrs?serverTimezone=UTC&rewriteBatchedStatements=true"); 31 | dataSource.setUsername("root"); 32 | dataSource.setPassword("root"); 33 | dataSource.setMaximumPoolSize(80); 34 | dataSource.setMinimumIdle(80); 35 | dataSource.setDriverClassName(com.mysql.cj.jdbc.Driver.class.getTypeName()); 36 | return dataSource; 37 | } 38 | 39 | @Bean 40 | public JdbcTemplate jdbcTemplate(DataSource dataSource) { 41 | return new JdbcTemplate(dataSource); 42 | } 43 | 44 | @Bean 45 | public CqrsConfig config(JdbcTemplate jdbcTemplate) { 46 | List list = Lists.newArrayList( 47 | DataSourceMapping.builder().dataSourceName("ds0").dataSource(dataSource()).tableNumber(4).build() 48 | ); 49 | DefaultEventShardingRouting route = new DefaultEventShardingRouting(); 50 | IEventStore store = new MysqlEventStore(list, 32, route); 51 | IAggregateSnapshootService aggregateSnapshootService = new DefaultAggregateSnapshootService(8, 6); 52 | IAggregateCache aggregateCache = new DefaultAggregateCaffeineCache(1024 * 1024, 60); 53 | AggregateSlotLock aggregateSlotLock = new AggregateSlotLock(4096); 54 | AggregateRecoveryService aggregateRecoveryService = new AggregateRecoveryService(store, aggregateCache, aggregateSlotLock); 55 | EventCommittingService eventCommittingService = new EventCommittingService(store, 8, 1024 * 4, 32, aggregateRecoveryService); 56 | CqrsConfig cqrsConfig = CqrsConfig.builder(). 57 | eventStore(store).aggregateSnapshootService(aggregateSnapshootService).aggregateCache(aggregateCache). 58 | aggregateSlotLock(aggregateSlotLock). 59 | eventCommittingService(eventCommittingService).build(); 60 | return cqrsConfig; 61 | } 62 | 63 | @Bean 64 | public RedPacketCommandService redPacketCommandService(CqrsConfig cqrsConfig) { 65 | return new RedPacketCommandService(cqrsConfig); 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /cqrs-message-rocketmq/src/main/java/com/damon/cqrs/rocketmq/DefaultMQProducer.java: -------------------------------------------------------------------------------- 1 | package com.damon.cqrs.rocketmq; 2 | 3 | import org.apache.rocketmq.client.impl.MQClientManager; 4 | import org.apache.rocketmq.client.impl.factory.MQClientInstance; 5 | import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; 6 | import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; 7 | import org.apache.rocketmq.remoting.RPCHook; 8 | 9 | import java.util.concurrent.ConcurrentMap; 10 | 11 | public class DefaultMQProducer extends org.apache.rocketmq.client.producer.DefaultMQProducer { 12 | 13 | @SuppressWarnings("unused") 14 | private RPCHook rpcHook; 15 | 16 | public DefaultMQProducer() { 17 | super(); 18 | } 19 | 20 | public DefaultMQProducer(RPCHook rpcHook) { 21 | super(rpcHook); 22 | this.rpcHook = rpcHook; 23 | } 24 | 25 | public DefaultMQProducer(String producerGroup) { 26 | super(producerGroup); 27 | } 28 | 29 | public DefaultMQProducer(String producerGroup, RPCHook rpcHook, boolean enableMsgTrace, String customizedTraceTopic, RPCHook rpcHook1) { 30 | super(producerGroup, rpcHook, enableMsgTrace, customizedTraceTopic); 31 | this.rpcHook = rpcHook1; 32 | } 33 | 34 | public DefaultMQProducer(String namespace, String producerGroup, RPCHook rpcHook) { 35 | super(namespace, producerGroup); 36 | this.rpcHook = rpcHook; 37 | } 38 | 39 | public DefaultMQProducer(String producerGroup, RPCHook rpcHook) { 40 | super(producerGroup, rpcHook); 41 | this.rpcHook = rpcHook; 42 | } 43 | 44 | public DefaultMQProducer(String producerGroup, boolean enableMsgTrace, RPCHook rpcHook) { 45 | super(producerGroup, enableMsgTrace); 46 | this.rpcHook = rpcHook; 47 | } 48 | 49 | 50 | public DefaultMQProducer(String producerGroup, boolean enableMsgTrace, String customizedTraceTopic, RPCHook rpcHook) { 51 | super(producerGroup, enableMsgTrace, customizedTraceTopic); 52 | this.rpcHook = rpcHook; 53 | } 54 | 55 | public DefaultMQProducer(String namespace, String producerGroup, RPCHook rpcHook, boolean enableMsgTrace, String customizedTraceTopic) { 56 | super(namespace, producerGroup, rpcHook, enableMsgTrace, customizedTraceTopic); 57 | this.rpcHook = rpcHook; 58 | } 59 | 60 | public TopicPublishInfo tryToFindTopicPublishInfo(String topic) { 61 | DefaultMQProducerImpl defaultMQProducerImpl = super.defaultMQProducerImpl; 62 | ConcurrentMap topicPublishInfoMap = defaultMQProducerImpl.getTopicPublishInfoTable(); 63 | TopicPublishInfo topicPublishInfo = topicPublishInfoMap.get(topic); 64 | MQClientInstance mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this, null); 65 | if (null == topicPublishInfo || !topicPublishInfo.ok()) { 66 | topicPublishInfoMap.putIfAbsent(topic, new TopicPublishInfo()); 67 | mQClientFactory.updateTopicRouteInfoFromNameServer(topic); 68 | topicPublishInfo = topicPublishInfoMap.get(topic); 69 | } 70 | 71 | if (!topicPublishInfo.isHaveTopicRouterInfo() && !topicPublishInfo.ok()) { 72 | mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this); 73 | topicPublishInfo = topicPublishInfoMap.get(topic); 74 | return topicPublishInfo; 75 | } else { 76 | return topicPublishInfo; 77 | } 78 | 79 | } 80 | 81 | 82 | } 83 | --------------------------------------------------------------------------------