├── .github └── workflows │ └── main.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── demo-images ├── demo_order_flow.png ├── demo_order_process_to_payment_flow.png ├── demo_stock_event_flow.png ├── order_flow.png ├── payment_flow.png └── stock_flow.png ├── emon-lib ├── Dockerfile ├── README.md ├── docker-compose.yml ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── splitet │ │ └── core │ │ └── api │ │ └── emon │ │ ├── configuration │ │ ├── ConsumerOffsetListenerConfiguration.java │ │ ├── EventListenConfiguration.java │ │ ├── HazelcastConfigurer.java │ │ ├── HazelcastOperationMapsConfig.java │ │ ├── InMemoryComponents.java │ │ ├── TopologyConfiguration.java │ │ └── hazelcast │ │ │ ├── InMemoryFailedEvent.java │ │ │ ├── InMemoryRestoredEvent.java │ │ │ ├── MapsConfig.java │ │ │ ├── MulticastConfig.java │ │ │ ├── NetworkInterfacesConfig.java │ │ │ ├── QuorumListenerForApplicationEvents.java │ │ │ └── UserCodeDeploymentConfig.java │ │ ├── domain │ │ ├── BaseEvent.java │ │ ├── HandledEvent.java │ │ ├── IHandledEvent.java │ │ ├── IProducedEvent.java │ │ ├── NoneHandled.java │ │ ├── OperationEvent.java │ │ ├── OperationEvents.java │ │ ├── Partition.java │ │ ├── ProducedEvent.java │ │ ├── ServiceData.java │ │ ├── SpanningService.java │ │ ├── Topic.java │ │ └── Topology.java │ │ └── service │ │ ├── ConsumerOffsetListener.java │ │ ├── ConsumerOffsetListenerContainerService.java │ │ ├── EventListenContainerService.java │ │ ├── EventListener.java │ │ ├── EventMessageListener.java │ │ ├── MultipleEventMessageListener.java │ │ ├── OperationExpirationListener.java │ │ ├── TopologyService.java │ │ └── processor │ │ ├── ConsumerOffsetChangeEntryProcessor.java │ │ └── EndOffsetSetter.java │ └── test │ └── java │ └── io │ └── splitet │ └── core │ └── pojos │ └── OperationTest.java ├── emon ├── Dockerfile ├── README.md ├── docker-compose.yml ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── splitet │ │ │ └── core │ │ │ └── api │ │ │ └── emon │ │ │ ├── EmonApplication.java │ │ │ ├── controller │ │ │ ├── CommandController.java │ │ │ ├── EventController.java │ │ │ ├── OperationController.java │ │ │ └── ServiceController.java │ │ │ ├── dto │ │ │ ├── ResponseDto.java │ │ │ ├── ServiceResponseDto.java │ │ │ └── TopicResponseDto.java │ │ │ └── service │ │ │ └── OperationsBroadcaster.java │ └── resources │ │ ├── application.yml │ │ └── bootstrap.yml │ └── test │ └── java │ └── io │ └── splitet │ └── core │ └── pojos │ └── OperationTest.java ├── java-api ├── README.md ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── splitet │ │ └── core │ │ ├── TestMain.java │ │ ├── api │ │ ├── Command.java │ │ ├── CommandDto.java │ │ ├── CommandHandler.java │ │ ├── EventHandler.java │ │ ├── EventRepository.java │ │ ├── IUserContext.java │ │ ├── IdCreationStrategy.java │ │ ├── RollbackCommandSpec.java │ │ ├── RollbackSpec.java │ │ ├── ViewQuery.java │ │ ├── Views.java │ │ └── impl │ │ │ ├── EmptyUserContext.java │ │ │ └── UUIDCreationStrategy.java │ │ ├── cassandra │ │ ├── BaseCassandraViewQuery.java │ │ ├── CassandraEventRecorder.java │ │ ├── CassandraSession.java │ │ ├── CassandraViewQuery.java │ │ ├── ConcurrencyResolver.java │ │ ├── ConcurrentEventException.java │ │ ├── ConcurrentEventResolver.java │ │ ├── DefaultConcurrencyResolver.java │ │ ├── EntityEvent.java │ │ ├── EventStoreConfig.java │ │ └── OptimizedCassandraViewQuery.java │ │ ├── common │ │ ├── CommandExecutionInterceptor.java │ │ ├── Context.java │ │ ├── EventExecutionInterceptor.java │ │ ├── EventKey.java │ │ ├── EventRecorder.java │ │ ├── EventType.java │ │ ├── OperationContext.java │ │ ├── PublishedEvent.java │ │ ├── ReceivedEvent.java │ │ └── RecordedEvent.java │ │ ├── core │ │ └── CompositeRepositoryImpl.java │ │ ├── exception │ │ ├── EventContextException.java │ │ ├── EventPulisherException.java │ │ └── EventStoreException.java │ │ ├── kafka │ │ ├── IOperationRepository.java │ │ ├── JsonDeserializer.java │ │ ├── JsonSerializer.java │ │ ├── KafkaOperationRepository.java │ │ ├── KafkaOperationRepositoryFactory.java │ │ ├── KafkaProperties.java │ │ ├── PublishedEventWrapper.java │ │ └── SerializableConsumer.java │ │ ├── pojos │ │ ├── CommandRecord.java │ │ ├── Event.java │ │ ├── EventState.java │ │ ├── IEventType.java │ │ ├── IOperationEvents.java │ │ ├── Operation.java │ │ └── TransactionState.java │ │ └── view │ │ ├── AggregateListener.java │ │ ├── BaseEntity.java │ │ ├── Entity.java │ │ ├── EntityEventWrapper.java │ │ ├── EntityFunction.java │ │ ├── EntityFunctionSpec.java │ │ ├── SnapshotOptimizedAggregateListener.java │ │ └── SnapshotRepository.java │ └── test │ └── java │ └── io │ └── splitet │ └── core │ ├── api │ └── impl │ │ ├── EmptyUserContextTest.java │ │ └── UUIDCreationStrategyTest.java │ ├── cassandra │ └── DefaultConcurrencyResolverTest.java │ ├── common │ └── OperationContextTest.java │ └── core │ └── CompositeRepositoryImplTest.java ├── pom.xml ├── resources ├── checkstyle-suppression.xml ├── checkstyle.xml ├── eventapis.png ├── eventapis.svg ├── findbugs-exclude.xml └── splitet_logo.png ├── spec ├── eventstore.md ├── samples │ ├── Sample Event Topology.png │ ├── sample-topology-1.json │ └── sample-topology-single.json ├── schema │ ├── Layers.png │ ├── command.request.json │ ├── command.response.json │ ├── context.json │ ├── entity-event.json │ ├── event-key.json │ ├── operation.json │ ├── published-event-wrapper.json │ └── published-event.json └── spec-1.0.md ├── spring-integration ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── splitet │ └── core │ └── spring │ ├── configuration │ ├── AggregateListenerService.java │ ├── AutomaticTopicConfiguration.java │ ├── DataMigrationService.java │ ├── EventApisConfiguration.java │ ├── EventApisFactory.java │ ├── EventMessageConverter.java │ ├── FeignHelper.java │ └── SpringKafkaOpListener.java │ └── filter │ └── OpContextFilter.java ├── spring-jpa-view ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── splitet │ └── core │ └── spring │ └── model │ └── JpaEntity.java └── travis └── settings.xml /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | contents: read 10 | packages: write 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up JDK 8 14 | uses: actions/setup-java@v2 15 | with: 16 | java-version: '8' 17 | distribution: 'adopt' 18 | architecture: x64 19 | - name: Build with Maven 20 | run: mvn --batch-mode -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn clean deploy 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | nbproject/private/ 21 | build/ 22 | nbbuild/ 23 | dist/ 24 | nbdist/ 25 | .nb-gradle/ -------------------------------------------------------------------------------- /demo-images/demo_order_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splitet/SplitetFramework/d75037c5e8af66e0d7eeb87135a884493a8f1458/demo-images/demo_order_flow.png -------------------------------------------------------------------------------- /demo-images/demo_order_process_to_payment_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splitet/SplitetFramework/d75037c5e8af66e0d7eeb87135a884493a8f1458/demo-images/demo_order_process_to_payment_flow.png -------------------------------------------------------------------------------- /demo-images/demo_stock_event_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splitet/SplitetFramework/d75037c5e8af66e0d7eeb87135a884493a8f1458/demo-images/demo_stock_event_flow.png -------------------------------------------------------------------------------- /demo-images/order_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splitet/SplitetFramework/d75037c5e8af66e0d7eeb87135a884493a8f1458/demo-images/order_flow.png -------------------------------------------------------------------------------- /demo-images/payment_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splitet/SplitetFramework/d75037c5e8af66e0d7eeb87135a884493a8f1458/demo-images/payment_flow.png -------------------------------------------------------------------------------- /demo-images/stock_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splitet/SplitetFramework/d75037c5e8af66e0d7eeb87135a884493a8f1458/demo-images/stock_flow.png -------------------------------------------------------------------------------- /emon-lib/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM frolvlad/alpine-oraclejdk8:slim 2 | MAINTAINER maaydin 3 | EXPOSE 8080 4 | ADD target/eventapis.jar / 5 | CMD java -jar eventapis.jar -------------------------------------------------------------------------------- /emon-lib/README.md: -------------------------------------------------------------------------------- 1 | # Eventapis Store 2 | An event database to enable event sourcing and distributed transactions for applications that use the microservice architecture. 3 | 4 | # Queues 5 | * Queue1 (id-QueueName: AGGREGATE_PARENT, Example: ORDER, ) 6 | * Events: 7 | * id: UUID 8 | * EventName: ORDER_CREATED (Ex: Order Created) 9 | * EventType: EXECUTE|FAIL 10 | * Params (Event Specific Data) 11 | * Transaction Ref (?) 12 | 13 | 14 | 15 | # Aggregate 16 | * Name: AGGREGATE_NAME (ex: SEND_PAYMENT) 17 | * Related Events 18 | * Execute Events (Ex: ORDER_CREATED|EXECUTE) 19 | * Fail Events (Ex: ORDER_CREATED|FAIL) 20 | 21 | 22 | # Transaction 23 | * id: UUID 24 | * StarterAggregate: Aggregate 25 | * Events 26 | * Event1 27 | * Event2 ... 28 | * State -------------------------------------------------------------------------------- /emon-lib/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | challenge: 4 | image: 'kloiasoft/eventapis:latest' -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/configuration/ConsumerOffsetListenerConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.configuration; 2 | 3 | 4 | import io.splitet.core.api.emon.service.ConsumerOffsetListener; 5 | import io.splitet.core.spring.configuration.EventApisConfiguration; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.kafka.clients.consumer.ConsumerConfig; 8 | import org.apache.kafka.common.serialization.ByteArrayDeserializer; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.kafka.core.DefaultKafkaConsumerFactory; 13 | import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; 14 | import org.springframework.kafka.listener.ContainerProperties; 15 | 16 | import java.util.Map; 17 | 18 | @Configuration 19 | @Slf4j 20 | public class ConsumerOffsetListenerConfiguration { 21 | 22 | public static final String CONSUMER_OFFSETS = "__consumer_offsets"; 23 | 24 | @Autowired 25 | private EventApisConfiguration eventApisConfiguration; 26 | 27 | @Autowired 28 | private ConsumerOffsetListener consumerOffsetListener; 29 | 30 | @Bean("consumerOffsetListenerContainer") 31 | public ConcurrentMessageListenerContainer consumerOffsetListenerContainer() { 32 | Map consumerProperties = eventApisConfiguration.getEventBus().buildConsumerProperties(); 33 | consumerProperties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); 34 | 35 | ContainerProperties containerProperties = new ContainerProperties(CONSUMER_OFFSETS); 36 | containerProperties.setMessageListener(consumerOffsetListener); 37 | containerProperties.setAckMode(ContainerProperties.AckMode.TIME); // To avoid echoings 38 | containerProperties.setAckTime(3000); 39 | 40 | DefaultKafkaConsumerFactory operationConsumerFactory = 41 | new DefaultKafkaConsumerFactory<>(consumerProperties, new ByteArrayDeserializer(), new ByteArrayDeserializer()); 42 | 43 | ConcurrentMessageListenerContainer consumerOffsetListenerContainer = 44 | new ConcurrentMessageListenerContainer<>(operationConsumerFactory, containerProperties); 45 | consumerOffsetListenerContainer.setConcurrency(5); 46 | consumerOffsetListenerContainer.setBeanName("consumer-offsets"); 47 | 48 | return consumerOffsetListenerContainer; 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/configuration/EventListenConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.configuration; 2 | 3 | import io.splitet.core.api.emon.service.EventMessageListener; 4 | import io.splitet.core.api.emon.service.MultipleEventMessageListener; 5 | import io.splitet.core.kafka.JsonDeserializer; 6 | import io.splitet.core.kafka.PublishedEventWrapper; 7 | import io.splitet.core.pojos.Operation; 8 | import io.splitet.core.spring.configuration.EventApisConfiguration; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.apache.kafka.clients.consumer.ConsumerConfig; 11 | import org.apache.kafka.common.serialization.StringDeserializer; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.Import; 17 | import org.springframework.kafka.core.DefaultKafkaConsumerFactory; 18 | import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; 19 | import org.springframework.kafka.listener.ContainerProperties; 20 | 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.regex.Pattern; 24 | 25 | @Configuration 26 | @Slf4j 27 | @Import(EventApisConfiguration.class) 28 | public class EventListenConfiguration { 29 | 30 | @Autowired 31 | private EventApisConfiguration eventApisConfiguration; 32 | 33 | @Autowired 34 | private List eventMessageListeners; 35 | 36 | @Value(value = "${eventapis.eventBus.eventTopicRegex:^.+Event$}") 37 | private String eventTopicRegexStr; 38 | 39 | @Value(value = "${eventapis.eventBus.consumerGroupRegex:^(.+-command-query|.+-command)$}") 40 | private String consumerGroupRegexStr; 41 | 42 | @Bean("eventTopicRegex") 43 | public Pattern eventTopicRegex() { 44 | return Pattern.compile(eventTopicRegexStr); 45 | } 46 | 47 | @Bean("consumerGroupRegex") 48 | public Pattern consumerGroupRegex() { 49 | return Pattern.compile(consumerGroupRegexStr); 50 | } 51 | 52 | @Bean(name = "operationListenerContainer") 53 | public ConcurrentMessageListenerContainer operationListenerContainer() { 54 | Map consumerProperties = eventApisConfiguration.getEventBus().buildConsumerProperties(); 55 | consumerProperties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); 56 | 57 | DefaultKafkaConsumerFactory operationConsumerFactory = 58 | new DefaultKafkaConsumerFactory<>(consumerProperties, new StringDeserializer(), new JsonDeserializer<>(Operation.class)); 59 | 60 | ContainerProperties containerProperties = new ContainerProperties(Operation.OPERATION_EVENTS); 61 | containerProperties.setMessageListener(new MultipleEventMessageListener(eventMessageListeners)); 62 | containerProperties.setAckMode(ContainerProperties.AckMode.BATCH); 63 | ConcurrentMessageListenerContainer operationListenerContainer = new ConcurrentMessageListenerContainer<>(operationConsumerFactory, containerProperties); 64 | operationListenerContainer.setBeanName("emon-operations"); 65 | operationListenerContainer.setConcurrency(eventApisConfiguration.getEventBus().getConsumer().getOperationConcurrency()); 66 | return operationListenerContainer; 67 | } 68 | 69 | @Bean(name = "messageListenerContainer") 70 | public ConcurrentMessageListenerContainer messageListenerContainer() { 71 | Map consumerProperties = eventApisConfiguration.getEventBus().buildConsumerProperties(); 72 | consumerProperties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); 73 | consumerProperties.put(ConsumerConfig.METADATA_MAX_AGE_CONFIG, 3000); 74 | 75 | DefaultKafkaConsumerFactory consumerFactory = 76 | new DefaultKafkaConsumerFactory<>(consumerProperties, new StringDeserializer(), new JsonDeserializer<>(PublishedEventWrapper.class)); 77 | 78 | ContainerProperties containerProperties = new ContainerProperties(Pattern.compile(eventTopicRegexStr)); 79 | containerProperties.setMessageListener(new MultipleEventMessageListener(eventMessageListeners)); 80 | containerProperties.setAckMode(ContainerProperties.AckMode.BATCH); 81 | ConcurrentMessageListenerContainer messageListenerContainer = new ConcurrentMessageListenerContainer<>(consumerFactory, containerProperties); 82 | messageListenerContainer.setConcurrency(eventApisConfiguration.getEventBus().getConsumer().getEventConcurrency()); 83 | messageListenerContainer.setBeanName("emon-events"); 84 | return messageListenerContainer; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/configuration/HazelcastConfigurer.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.configuration; 2 | 3 | import com.hazelcast.config.Config; 4 | 5 | public interface HazelcastConfigurer { 6 | 7 | Config configure(Config existingConfig); 8 | } 9 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/configuration/HazelcastOperationMapsConfig.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.configuration; 2 | 3 | import com.hazelcast.config.Config; 4 | import com.hazelcast.config.EvictionPolicy; 5 | import com.hazelcast.config.MapConfig; 6 | import com.hazelcast.config.MapIndexConfig; 7 | import com.hazelcast.config.MaxSizeConfig; 8 | import com.hazelcast.core.HazelcastInstance; 9 | import com.hazelcast.core.IMap; 10 | import io.splitet.core.api.emon.domain.OperationEvents; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.beans.factory.annotation.Qualifier; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.core.annotation.Order; 17 | 18 | import java.util.Arrays; 19 | import java.util.List; 20 | 21 | @Configuration 22 | @Order 23 | public class HazelcastOperationMapsConfig implements HazelcastConfigurer { 24 | 25 | public static final String EVENTS_OPS_MAP_NAME = "events-op"; 26 | 27 | @Value("${emon.hazelcast.evict.freeHeapPercentage:10}") 28 | private Integer evictFreePercentage; 29 | 30 | @Override 31 | public Config configure(Config config) { 32 | 33 | List indexes = Arrays.asList( 34 | new MapIndexConfig("spanningServices[any].serviceName", true) 35 | ); 36 | 37 | MapConfig mapConfig = new MapConfig(EVENTS_OPS_MAP_NAME) 38 | .setMapIndexConfigs(indexes) 39 | .setMaxSizeConfig(new MaxSizeConfig(evictFreePercentage, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_PERCENTAGE)) 40 | .setEvictionPolicy(EvictionPolicy.LRU) 41 | .setQuorumName("default") 42 | .setName(EVENTS_OPS_MAP_NAME); 43 | config.addMapConfig(mapConfig); 44 | return config; 45 | } 46 | 47 | @Bean 48 | public IMap eventsOperationsMap(@Autowired @Qualifier("hazelcastInstance") HazelcastInstance hazelcastInstance) { 49 | return hazelcastInstance.getMap(EVENTS_OPS_MAP_NAME); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/configuration/InMemoryComponents.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.configuration; 2 | 3 | import com.hazelcast.config.Config; 4 | import com.hazelcast.config.GroupConfig; 5 | import com.hazelcast.config.InterfacesConfig; 6 | import com.hazelcast.core.Hazelcast; 7 | import com.hazelcast.core.HazelcastInstance; 8 | import com.hazelcast.core.IMap; 9 | import com.hazelcast.core.ITopic; 10 | import com.hazelcast.spring.context.SpringManagedContext; 11 | import io.splitet.core.api.emon.configuration.hazelcast.MulticastConfig; 12 | import io.splitet.core.api.emon.configuration.hazelcast.UserCodeDeploymentConfig; 13 | import io.splitet.core.api.emon.domain.Topic; 14 | import io.splitet.core.api.emon.domain.Topology; 15 | import io.splitet.core.api.emon.service.OperationExpirationListener; 16 | import lombok.extern.slf4j.Slf4j; 17 | import org.springframework.beans.factory.annotation.Autowired; 18 | import org.springframework.beans.factory.annotation.Qualifier; 19 | import org.springframework.beans.factory.annotation.Value; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.Configuration; 22 | import org.springframework.context.annotation.Import; 23 | import org.springframework.context.annotation.Primary; 24 | 25 | import javax.annotation.PreDestroy; 26 | import java.util.List; 27 | 28 | @Configuration 29 | @Import({MulticastConfig.class, InterfacesConfig.class, UserCodeDeploymentConfig.class}) 30 | @Slf4j 31 | public class InMemoryComponents { 32 | 33 | 34 | @Value("${emon.hazelcast.group.name:'emon'}") 35 | private String hazelcastGrid; 36 | @Value("${emon.hazelcast.group.password:'emon123'}") 37 | private String hazelcastPassword; 38 | @Value("${emon.hazelcast.evict.freeHeapPercentage:20}") 39 | private Integer evictFreePercentage; 40 | 41 | @Value("${eventapis.eventBus.consumer.groupId}") 42 | // @Value("${info.build.artifact}") 43 | private String artifactId; 44 | private HazelcastInstance hazelcastInstance; 45 | 46 | @Autowired 47 | private List hazelcastConfigurers; 48 | 49 | @Bean 50 | public Config config() { 51 | Config config = new Config(); 52 | GroupConfig groupConfig = config.getGroupConfig(); 53 | groupConfig.setName(hazelcastGrid); 54 | groupConfig.setPassword(hazelcastPassword); 55 | config.setGroupConfig(groupConfig); 56 | config.setInstanceName(artifactId); 57 | 58 | for (HazelcastConfigurer hazelcastConfigurer : hazelcastConfigurers) { 59 | config = hazelcastConfigurer.configure(config); 60 | } 61 | return config; 62 | } 63 | 64 | @Bean 65 | public SpringManagedContext managedContext() { 66 | return new SpringManagedContext(); 67 | } 68 | 69 | @Bean 70 | @Primary 71 | public HazelcastInstance hazelcastInstance(Config config, SpringManagedContext springManagedContext) { 72 | config.setManagedContext(springManagedContext); 73 | hazelcastInstance = Hazelcast.newHazelcastInstance(config); 74 | return this.hazelcastInstance; 75 | } 76 | 77 | @Bean 78 | public OperationExpirationListener operationExpirationListener(@Autowired @Qualifier("operationsHistoryMap") IMap operationsHistoryMap, 79 | @Autowired @Qualifier("topicsMap") IMap topicsMap, 80 | @Autowired @Qualifier("operationsTopic") ITopic operationsTopic) { 81 | return new OperationExpirationListener(operationsHistoryMap, topicsMap, operationsTopic); 82 | } 83 | 84 | @PreDestroy 85 | public void destroy() { 86 | hazelcastInstance.shutdown(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/configuration/TopologyConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.configuration; 2 | 3 | import io.splitet.core.kafka.JsonDeserializer; 4 | import io.splitet.core.pojos.Operation; 5 | import io.splitet.core.spring.configuration.EventApisConfiguration; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.kafka.clients.admin.AdminClient; 8 | import org.apache.kafka.clients.consumer.Consumer; 9 | import org.apache.kafka.common.serialization.StringDeserializer; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Qualifier; 12 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.context.annotation.Import; 16 | import org.springframework.kafka.core.DefaultKafkaConsumerFactory; 17 | 18 | import java.util.Properties; 19 | 20 | @Configuration 21 | @Import(EventApisConfiguration.class) 22 | @Slf4j 23 | @ConditionalOnProperty(value = "emon.offsetScheduler.enabled", havingValue = "true") 24 | public class TopologyConfiguration { 25 | 26 | @Autowired 27 | private EventApisConfiguration eventApisConfiguration; 28 | 29 | 30 | @Bean(name = "kafkaAdminProperties") 31 | public Properties kafkaAdminProperties() { 32 | String bootstrapServers = String.join(",", eventApisConfiguration.getEventBus().getBootstrapServers()); 33 | String zookeeperServers = String.join(",", eventApisConfiguration.getEventBus().getZookeeperServers()); 34 | Properties properties = new Properties(); 35 | properties.putAll(eventApisConfiguration.getEventBus().buildCommonProperties()); 36 | properties.put("zookeeper", zookeeperServers); 37 | properties.put("bootstrap.servers", bootstrapServers); 38 | return properties; 39 | } 40 | 41 | @Bean("adminClient") 42 | public AdminClient adminClient(@Autowired @Qualifier("kafkaAdminProperties") Properties kafkaAdminProperties) { 43 | return AdminClient.create(kafkaAdminProperties); 44 | } 45 | 46 | @Bean 47 | public Consumer kafkaConsumer() { 48 | return new DefaultKafkaConsumerFactory<>( 49 | eventApisConfiguration.getEventBus().buildConsumerProperties(), 50 | new StringDeserializer(), 51 | new JsonDeserializer<>(Operation.class)).createConsumer(); 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/configuration/hazelcast/InMemoryFailedEvent.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.configuration.hazelcast; 2 | 3 | import org.springframework.context.ApplicationEvent; 4 | 5 | public class InMemoryFailedEvent extends ApplicationEvent { 6 | /** 7 | * Create a new ApplicationEvent. 8 | * 9 | * @param source the object on which the event initially occurred (never {@code null}) 10 | */ 11 | public InMemoryFailedEvent(Object source) { 12 | super(source); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/configuration/hazelcast/InMemoryRestoredEvent.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.configuration.hazelcast; 2 | 3 | import org.springframework.context.ApplicationEvent; 4 | 5 | public class InMemoryRestoredEvent extends ApplicationEvent { 6 | /** 7 | * Create a new ApplicationEvent. 8 | * 9 | * @param source the object on which the event initially occurred (never {@code null}) 10 | */ 11 | public InMemoryRestoredEvent(Object source) { 12 | super(source); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/configuration/hazelcast/MapsConfig.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.configuration.hazelcast; 2 | 3 | import com.hazelcast.config.Config; 4 | import com.hazelcast.config.EvictionPolicy; 5 | import com.hazelcast.config.MapIndexConfig; 6 | import com.hazelcast.config.MaxSizeConfig; 7 | import com.hazelcast.config.MulticastConfig; 8 | import com.hazelcast.core.HazelcastInstance; 9 | import com.hazelcast.core.IMap; 10 | import com.hazelcast.core.ITopic; 11 | import io.splitet.core.api.emon.configuration.HazelcastConfigurer; 12 | import io.splitet.core.api.emon.domain.Topic; 13 | import io.splitet.core.api.emon.domain.Topology; 14 | import io.splitet.core.api.emon.service.OperationExpirationListener; 15 | import lombok.Data; 16 | import lombok.ToString; 17 | import org.springframework.beans.factory.annotation.Autowired; 18 | import org.springframework.beans.factory.annotation.Qualifier; 19 | import org.springframework.beans.factory.annotation.Value; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.Configuration; 22 | 23 | import java.util.Arrays; 24 | import java.util.List; 25 | 26 | @Data 27 | @Configuration 28 | @ToString(callSuper = true) 29 | public class MapsConfig extends MulticastConfig implements HazelcastConfigurer { 30 | public static final int OPERATIONS_MAX_TTL_INSEC = 60000; 31 | public static final String OPERATIONS_MAP_NAME = "operations"; 32 | public static final String OPERATIONS_MAP_HISTORY_NAME = "operations-history"; 33 | public static final String META_MAP_NAME = "meta"; 34 | public static final String TOPICS_MAP_NAME = "topics"; 35 | public static final String COMMANDS_MAP_NAME = "commands"; 36 | public static final String OPERATIONS_TOPICS_NAME = "operations_topic"; 37 | 38 | @Value("${emon.hazelcast.evict.freeHeapPercentage:20}") 39 | private Integer evictFreePercentage; 40 | 41 | @Override 42 | public Config configure(Config config) { 43 | 44 | List indexes = Arrays.asList( 45 | new MapIndexConfig("startTime", true), 46 | new MapIndexConfig("operationState", true) 47 | ); 48 | config.getMapConfig(OPERATIONS_MAP_NAME) 49 | .setTimeToLiveSeconds(OPERATIONS_MAX_TTL_INSEC) 50 | .setMapIndexConfigs(indexes); 51 | 52 | config.getMapConfig(OPERATIONS_MAP_HISTORY_NAME) 53 | .setMapIndexConfigs(indexes) 54 | .setMaxSizeConfig(new MaxSizeConfig(evictFreePercentage, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_PERCENTAGE)) 55 | .setEvictionPolicy(EvictionPolicy.LRU); 56 | config.getReplicatedMapConfig(TOPICS_MAP_NAME); 57 | 58 | config.getTopicConfig(OPERATIONS_TOPICS_NAME); 59 | 60 | return config; 61 | } 62 | 63 | @Bean 64 | @Qualifier("operationsMap") 65 | public IMap operationsMap(@Autowired @Qualifier("hazelcastInstance") HazelcastInstance hazelcastInstance, OperationExpirationListener operationExpirationListener) { 66 | IMap operationsMap = hazelcastInstance.getMap(OPERATIONS_MAP_NAME); 67 | operationsMap.addLocalEntryListener(operationExpirationListener); 68 | return operationsMap; 69 | } 70 | 71 | @Bean 72 | @Qualifier("operationsHistoryMap") 73 | public IMap operationsHistoryMap(@Autowired @Qualifier("hazelcastInstance") HazelcastInstance hazelcastInstance) { 74 | return hazelcastInstance.getMap(OPERATIONS_MAP_HISTORY_NAME); 75 | } 76 | 77 | @Bean 78 | @Qualifier("topicsMap") 79 | public IMap topicsMap(@Autowired @Qualifier("hazelcastInstance") HazelcastInstance hazelcastInstance) { 80 | return hazelcastInstance.getMap(TOPICS_MAP_NAME); 81 | } 82 | 83 | @Bean 84 | @Qualifier("commandsMap") 85 | public IMap commandsMap(@Autowired @Qualifier("hazelcastInstance") HazelcastInstance hazelcastInstance) { 86 | return hazelcastInstance.getMap(COMMANDS_MAP_NAME); 87 | } 88 | 89 | @Bean 90 | @Qualifier("operationsTopic") 91 | public ITopic operationsTopic(@Autowired @Qualifier("hazelcastInstance") HazelcastInstance hazelcastInstance) { 92 | return hazelcastInstance.getTopic(OPERATIONS_TOPICS_NAME); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/configuration/hazelcast/MulticastConfig.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.configuration.hazelcast; 2 | 3 | import com.hazelcast.config.Config; 4 | import io.splitet.core.api.emon.configuration.HazelcastConfigurer; 5 | import lombok.Data; 6 | import lombok.ToString; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.boot.context.properties.ConfigurationProperties; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Data 12 | @Configuration 13 | @ConditionalOnProperty(value = "emon.hazelcast.discovery.type", havingValue = "multicast") 14 | @ConfigurationProperties(prefix = "emon.hazelcast.discovery.multicast") 15 | @ToString(callSuper = true) 16 | public class MulticastConfig extends com.hazelcast.config.MulticastConfig implements HazelcastConfigurer { 17 | @Override 18 | public Config configure(Config config) { 19 | config.getNetworkConfig().getJoin().setMulticastConfig(this); 20 | return config; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/configuration/hazelcast/NetworkInterfacesConfig.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.configuration.hazelcast; 2 | 3 | import com.hazelcast.config.Config; 4 | import com.hazelcast.config.InterfacesConfig; 5 | import io.splitet.core.api.emon.configuration.HazelcastConfigurer; 6 | import lombok.Data; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.boot.context.properties.ConfigurationProperties; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Data 12 | @Configuration 13 | @ConditionalOnProperty(value = "emon.hazelcast.interfaces.enabled", havingValue = "true") 14 | @ConfigurationProperties(prefix = "emon.hazelcast.interfaces") 15 | public class NetworkInterfacesConfig extends InterfacesConfig implements HazelcastConfigurer { 16 | 17 | @Override 18 | public Config configure(Config config) { 19 | config.getNetworkConfig().setInterfaces(this); 20 | return config; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/configuration/hazelcast/QuorumListenerForApplicationEvents.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.configuration.hazelcast; 2 | 3 | import com.hazelcast.quorum.QuorumEvent; 4 | import com.hazelcast.quorum.QuorumListener; 5 | import org.springframework.context.ApplicationEventPublisher; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class QuorumListenerForApplicationEvents implements QuorumListener { 10 | private final ApplicationEventPublisher publisher; 11 | 12 | public QuorumListenerForApplicationEvents(ApplicationEventPublisher publisher) { 13 | this.publisher = publisher; 14 | } 15 | 16 | @Override 17 | public void onChange(QuorumEvent quorumEvent) { 18 | if(quorumEvent.isPresent()) 19 | publisher.publishEvent(new InMemoryRestoredEvent(quorumEvent)); 20 | else 21 | publisher.publishEvent(new InMemoryFailedEvent(quorumEvent)); 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/configuration/hazelcast/UserCodeDeploymentConfig.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.configuration.hazelcast; 2 | 3 | import com.hazelcast.config.Config; 4 | import io.splitet.core.api.emon.configuration.HazelcastConfigurer; 5 | import lombok.Data; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | @Data 11 | @Configuration 12 | @ConditionalOnProperty(value = "emon.hazelcast.user-code-deployment.enabled", havingValue = "true") 13 | @ConfigurationProperties(prefix = "emon.hazelcast.user-code-deployment") 14 | public class UserCodeDeploymentConfig extends com.hazelcast.config.UserCodeDeploymentConfig implements HazelcastConfigurer { 15 | 16 | @Override 17 | public Config configure(Config config) { 18 | config.setUserCodeDeploymentConfig(this); 19 | return config; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/domain/BaseEvent.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import io.splitet.core.common.ReceivedEvent; 5 | import lombok.Data; 6 | 7 | @Data 8 | @JsonIgnoreProperties(ignoreUnknown = true) 9 | public class BaseEvent extends ReceivedEvent { 10 | } 11 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/domain/HandledEvent.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 4 | import com.fasterxml.jackson.annotation.JsonTypeName; 5 | import io.splitet.core.pojos.TransactionState; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.util.HashSet; 10 | import java.util.Objects; 11 | import java.util.Optional; 12 | import java.util.Set; 13 | 14 | @Slf4j 15 | @Data 16 | @JsonTypeName("handled") 17 | @JsonPropertyOrder({"handlerService", "topic", "finished", "operation", "producedEvents"}) 18 | public class HandledEvent implements IHandledEvent { 19 | 20 | private String handlerService; 21 | private String topic; 22 | private OperationEvent operation; 23 | private Set producedEvents = new HashSet<>(); 24 | 25 | public HandledEvent(ProducedEvent producedEvent, String handlerService, String topic) { 26 | this.handlerService = handlerService; 27 | this.topic = topic; 28 | producedEvents.add(producedEvent); 29 | } 30 | 31 | 32 | public void attachProducedEvent(ProducedEvent producedEvent) { 33 | Optional first = producedEvents.stream().filter(existingEvent -> Objects.equals(existingEvent.getTopic(), producedEvent.getTopic())).findFirst(); 34 | if (first.isPresent()) { 35 | first.get().incrementNumberOfVisit(); 36 | log.info("Duplicate Event Handle for:" + producedEvent); 37 | } else { 38 | producedEvents.add(producedEvent); 39 | } 40 | } 41 | 42 | @Override 43 | public boolean isFinished() { 44 | return producedEvents.stream().allMatch(ProducedEvent::isFinished); 45 | } 46 | 47 | @Override 48 | public boolean attachOperation(OperationEvent operationToAttach) { 49 | if (Objects.equals(operationToAttach.getSender(), getHandlerService()) 50 | && Objects.equals(operationToAttach.getAggregateId(), topic) 51 | && operationToAttach.getTransactionState() == TransactionState.TXN_SUCCEEDED) { 52 | this.operation = operationToAttach; 53 | return true; 54 | } 55 | return producedEvents.stream().anyMatch(producedEvent -> producedEvent.attachOperation(operationToAttach)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/domain/IHandledEvent.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonSubTypes; 4 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 5 | 6 | import java.io.Serializable; 7 | 8 | 9 | @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, 10 | include = JsonTypeInfo.As.PROPERTY, 11 | property = "type") 12 | @JsonSubTypes({ 13 | @JsonSubTypes.Type(value = HandledEvent.class, name = "handled"), 14 | @JsonSubTypes.Type(value = NoneHandled.class, name = "none") 15 | }) 16 | public interface IHandledEvent extends Serializable { 17 | boolean isFinished(); 18 | 19 | void setOperation(OperationEvent operation); 20 | 21 | boolean attachOperation(OperationEvent operationToAttach); 22 | } 23 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/domain/IProducedEvent.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonSubTypes; 4 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 5 | 6 | import java.io.Serializable; 7 | 8 | @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, 9 | include = JsonTypeInfo.As.PROPERTY, 10 | property = "type") 11 | @JsonSubTypes({ 12 | @JsonSubTypes.Type(value = ProducedEvent.class, name = "event") 13 | }) 14 | public interface IProducedEvent extends Serializable { 15 | 16 | boolean attachHandler(ProducedEvent eventHandler); 17 | 18 | void incrementNumberOfVisit(); 19 | 20 | boolean isFinished(); 21 | 22 | boolean attachOperation(OperationEvent operation); 23 | 24 | void setOperation(OperationEvent operation); 25 | } 26 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/domain/NoneHandled.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonTypeName; 5 | import io.splitet.core.pojos.TransactionState; 6 | import lombok.Data; 7 | 8 | @Data 9 | @JsonTypeName("none") 10 | @JsonInclude(JsonInclude.Include.NON_NULL) 11 | public class NoneHandled implements IHandledEvent { 12 | 13 | private OperationEvent operation; 14 | private boolean finishedAsLeaf = false; 15 | 16 | NoneHandled() { 17 | } 18 | 19 | @Override 20 | public boolean isFinished() { 21 | if (operation != null) 22 | return operation.getTransactionState() == TransactionState.TXN_FAILED; 23 | else return finishedAsLeaf; 24 | } 25 | 26 | @Override 27 | public boolean attachOperation(OperationEvent operationToAttach) { 28 | return false; 29 | } 30 | 31 | @Override 32 | public void setOperation(OperationEvent operation) { 33 | this.operation = operation; 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/domain/OperationEvent.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.domain; 2 | 3 | import io.splitet.core.common.Context; 4 | import io.splitet.core.pojos.Operation; 5 | import io.splitet.core.pojos.TransactionState; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.io.Serializable; 11 | 12 | /** 13 | * Created by zeldalozdemir on 25/01/2017. 14 | */ 15 | @Data 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class OperationEvent implements Serializable { 19 | 20 | 21 | private static final long serialVersionUID = 3269757297830153667L; 22 | private TransactionState transactionState; 23 | private String aggregateId; 24 | private String sender; 25 | private long opDate; 26 | private Context context; 27 | 28 | public OperationEvent(Operation operation) { 29 | this.transactionState = operation.getTransactionState(); 30 | this.aggregateId = operation.getAggregateId(); 31 | this.sender = operation.getSender(); 32 | this.context = operation.getContext(); 33 | this.opDate = operation.getOpDate() != 0L ? operation.getOpDate() : System.currentTimeMillis(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/domain/OperationEvents.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import io.splitet.core.pojos.TransactionState; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | @Data 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | @JsonFormat(shape = JsonFormat.Shape.OBJECT) 19 | public class OperationEvents { 20 | 21 | private static final long serialVersionUID = -2419694872838243026L; 22 | 23 | @JsonProperty 24 | private TransactionState transactionState = TransactionState.RUNNING; 25 | 26 | @JsonProperty 27 | private boolean finished; 28 | 29 | @JsonProperty 30 | private List spanningServices = new ArrayList<>(); 31 | 32 | 33 | @JsonIgnore 34 | private Map userContext; 35 | 36 | public OperationEvents(Map userContext) { 37 | this.userContext = userContext; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/domain/Partition.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | 10 | @Data 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | @JsonInclude(JsonInclude.Include.NON_NULL) 14 | public class Partition implements Serializable { 15 | private static final long serialVersionUID = -4771113118830062688L; 16 | 17 | private int number; 18 | private Long offset = 0L; 19 | private Long lag; 20 | 21 | public Partition(int number) { 22 | this.number = number; 23 | } 24 | 25 | public Partition(int number, Long offset) { 26 | this.number = number; 27 | this.offset = offset; 28 | } 29 | 30 | public void calculateLag(long endOffset) { 31 | if (endOffset > offset) 32 | lag = endOffset - offset; 33 | else lag = null; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/domain/ServiceData.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | import java.util.Collection; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.function.Function; 14 | import java.util.stream.Collectors; 15 | 16 | @Data 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | @JsonInclude(JsonInclude.Include.NON_NULL) 20 | public class ServiceData implements Serializable { 21 | private static final long serialVersionUID = 2401532791975588L; 22 | 23 | private String serviceName; 24 | private Map partitions = new HashMap<>(); 25 | 26 | public ServiceData(String serviceName, List partitionList) { 27 | this.serviceName = serviceName; 28 | this.setPartitions(partitionList); 29 | } 30 | 31 | 32 | public static ServiceData createServiceData(String consumer, List value) { 33 | return new ServiceData(consumer, value.stream().collect(Collectors.toMap(Partition::getNumber, Function.identity()))); 34 | } 35 | 36 | public Partition getPartition(int number) { 37 | if (partitions != null) 38 | return partitions.get(number); 39 | else 40 | return null; 41 | } 42 | 43 | public Partition setPartition(Partition partition) { 44 | return partitions.put(partition.getNumber(), partition); 45 | } 46 | 47 | public void setPartitions(Collection partitionList) { 48 | partitions = partitionList.stream().collect(Collectors.toMap(Partition::getNumber, Function.identity())); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/domain/SpanningService.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.NotNull; 8 | import java.io.Serializable; 9 | 10 | @Data 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class SpanningService implements Comparable, Serializable { 14 | 15 | private static final long serialVersionUID = 7156177214906375549L; 16 | 17 | private String serviceName; 18 | private boolean isConsumed; 19 | 20 | @Override 21 | public int compareTo(@NotNull SpanningService spanningService) { 22 | return serviceName.compareTo(spanningService.getServiceName()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/domain/Topic.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.io.Serializable; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Optional; 14 | import java.util.function.Function; 15 | import java.util.stream.Collectors; 16 | 17 | @Slf4j 18 | @Data 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | @JsonInclude(JsonInclude.Include.NON_EMPTY) 22 | public class Topic implements Serializable { 23 | 24 | private static final long serialVersionUID = -8668554375371818043L; 25 | 26 | private Map serviceDataHashMap = new HashMap<>(); 27 | 28 | private Map partitions = new HashMap<>(); 29 | 30 | public static Topic createTopic(String serviceName, int partitionNo, Long offset) { 31 | ServiceData serviceData = new ServiceData(serviceName, new HashMap<>()); 32 | serviceData.setPartition(new Partition(partitionNo, offset)); 33 | 34 | HashMap serviceDataHashMap = new HashMap<>(); 35 | serviceDataHashMap.put(serviceName, serviceData); 36 | return new Topic(serviceDataHashMap, new HashMap<>()); 37 | } 38 | 39 | public Map getServiceDataHashMap() { 40 | try { 41 | serviceDataHashMap.forEach((s, serviceData) -> serviceData.getPartitions().values() 42 | .forEach(partition -> getPartition(partition.getNumber()).ifPresent( 43 | partition1 -> partition.calculateLag(partition1.getOffset())) 44 | ) 45 | ); 46 | } catch (Exception ex) { 47 | log.warn(ex.getMessage()); 48 | } 49 | return serviceDataHashMap; 50 | } 51 | 52 | private Optional getPartition(int number) { 53 | return Optional.ofNullable(partitions.get(number)); 54 | } 55 | 56 | public void setPartitions(List value) { 57 | partitions = value.stream().collect(Collectors.toMap(Partition::getNumber, Function.identity())); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/domain/Topology.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 5 | import io.splitet.core.api.emon.service.TopologyService; 6 | import io.splitet.core.pojos.TransactionState; 7 | import lombok.Data; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.io.Serializable; 11 | import java.util.HashSet; 12 | import java.util.Objects; 13 | import java.util.Set; 14 | 15 | @Data 16 | @Slf4j 17 | @JsonInclude(JsonInclude.Include.NON_EMPTY) 18 | @JsonPropertyOrder({"opId", "parentOpId", "initiatorService", "initiatorCommand", "startTime", "endTime", "commandTimeout", "commandExecutionTime", "timedOut", 19 | "finished", "operation", "operationState", "unassignedOperations", "unassignedEvents", "producedEvents"}) 20 | public class Topology implements Serializable { 21 | 22 | private String parentOpId; 23 | private String opId; 24 | private String initiatorService; 25 | private String initiatorCommand; 26 | private long startTime; 27 | private long endTime; 28 | private long commandTimeout; 29 | private OperationEvent operation; 30 | private TransactionState operationState = TransactionState.RUNNING; 31 | private Set unassignedOperations = new HashSet<>(); 32 | private Set unassignedEvents = new HashSet<>(); 33 | private Set producedEvents = new HashSet<>(); 34 | 35 | public Topology() { 36 | } 37 | 38 | public Topology(String opId, String parentOpId) { 39 | this.opId = opId; 40 | this.parentOpId = parentOpId; 41 | } 42 | 43 | public Topology(String opId, String parentOpId, ProducedEvent head, String initiatorCommand) { 44 | this.opId = opId; 45 | this.parentOpId = parentOpId; 46 | this.producedEvents.add(head); 47 | this.initiatorService = head.getSender(); 48 | this.initiatorCommand = initiatorCommand; 49 | } 50 | 51 | public boolean attachProducedEvent(ProducedEvent producedEvent) { 52 | boolean result; 53 | if (Objects.equals(producedEvent.getAggregateId(), initiatorCommand) && Objects.equals(producedEvent.getSender(), initiatorService)) { 54 | result = producedEvents.add(producedEvent); 55 | } else 56 | result = producedEvents.stream().anyMatch(existingEvent -> existingEvent.attachHandler(producedEvent)); 57 | if (result) 58 | consumeStaleEvents(); 59 | else 60 | unassignedEvents.add(producedEvent); 61 | return result; 62 | } 63 | 64 | private void consumeStaleEvents() { 65 | this.unassignedEvents.removeIf(producedEvent -> producedEvents.stream().anyMatch(existingEvent -> existingEvent.attachHandler(producedEvent))); 66 | consumeStaleOperations(); 67 | } 68 | 69 | private void consumeStaleOperations() { 70 | if (unassignedOperations.isEmpty()) 71 | return; 72 | Set toConsume = this.unassignedOperations; 73 | this.unassignedOperations = new HashSet<>(); 74 | log.debug("Trying to Consume stales:" + toConsume); 75 | toConsume.forEach(this::attachOperation); 76 | } 77 | 78 | public boolean isFinished() { 79 | return operationState != TransactionState.RUNNING && producedEvents.stream().allMatch(ProducedEvent::isFinished); 80 | } 81 | 82 | public long getCommandExecutionTime() { 83 | return endTime - startTime; 84 | } 85 | 86 | public boolean isTimedOut() { 87 | return endTime - startTime > (commandTimeout + TopologyService.GRACE_PERIOD_IN_MILLIS); 88 | } 89 | 90 | public void attachOperation(OperationEvent operation) { 91 | if (operationState != TransactionState.RUNNING) 92 | log.error("Topology is Already ended with State:" + operationState + " New Operation: " + operation); 93 | this.endTime = operation.getOpDate(); 94 | if (Objects.equals(operation.getSender(), getInitiatorService()) && Objects.equals(operation.getAggregateId(), getInitiatorCommand())) { 95 | operationState = operation.getTransactionState(); 96 | this.operation = operation; 97 | } else { 98 | boolean result = producedEvents.stream().anyMatch(producedEvent -> producedEvent.attachOperation(operation)); 99 | if (result) 100 | operationState = operation.getTransactionState(); 101 | else { 102 | log.debug("We Couldn't attach, " + opId + " Adding to UnAssigned Operation Event:" + operation); 103 | unassignedOperations.add(operation); 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/service/ConsumerOffsetListener.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.service; 2 | 3 | import com.hazelcast.core.IMap; 4 | import io.splitet.core.api.emon.domain.Partition; 5 | import io.splitet.core.api.emon.domain.Topic; 6 | import io.splitet.core.api.emon.service.processor.ConsumerOffsetChangeEntryProcessor; 7 | import io.splitet.core.pojos.Operation; 8 | import kafka.common.OffsetAndMetadata; 9 | import kafka.coordinator.group.BaseKey; 10 | import kafka.coordinator.group.GroupMetadataManager; 11 | import kafka.coordinator.group.GroupTopicPartition; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.apache.kafka.clients.consumer.ConsumerRecord; 14 | import org.apache.kafka.common.TopicPartition; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.kafka.listener.ConsumerSeekAware; 17 | import org.springframework.kafka.listener.MessageListener; 18 | import org.springframework.stereotype.Service; 19 | 20 | import java.nio.ByteBuffer; 21 | import java.util.Map; 22 | import java.util.regex.Pattern; 23 | 24 | @Service 25 | @Slf4j 26 | public class ConsumerOffsetListener implements MessageListener, ConsumerSeekAware { 27 | 28 | 29 | private transient Pattern eventTopicRegex; 30 | private transient Pattern consumerGroupRegex; 31 | 32 | @Autowired 33 | private IMap topicsMap; 34 | 35 | public void onMessage(ConsumerRecord data) { 36 | BaseKey baseKey = GroupMetadataManager.readMessageKey(ByteBuffer.wrap(data.key())); 37 | if (!(baseKey.key() instanceof GroupTopicPartition)) { 38 | log.warn("Type of Base Key: {} Key: {}", baseKey.key() != null ? baseKey.key().getClass().getName() : null, baseKey.key()); 39 | return; 40 | } 41 | GroupTopicPartition key = (GroupTopicPartition) baseKey.key(); 42 | if (data.value() == null || data.value().length == 0) { 43 | log.warn("Value is null or Empty for: {}", key.toString()); 44 | return; 45 | } 46 | OffsetAndMetadata offsetAndMetadata = GroupMetadataManager.readOffsetMessageValue(ByteBuffer.wrap(data.value())); 47 | 48 | String group = key.group(); 49 | int partitionNo = key.topicPartition().partition(); 50 | String topic = key.topicPartition().topic(); 51 | long offset = offsetAndMetadata.offset(); 52 | if (!(shouldCollectEvent(topic) && shouldCollectConsumer(group))) { 53 | return; 54 | } 55 | 56 | Partition oldPartition = (Partition) topicsMap.executeOnKey(topic, new ConsumerOffsetChangeEntryProcessor(group, partitionNo, offset)); 57 | 58 | if (oldPartition != null) { 59 | log.info("Changed Old Partition: {} {} {} newOffset: {}", topic, group, oldPartition.toString(), offset); 60 | } 61 | 62 | } 63 | 64 | 65 | @Autowired 66 | public void setEventTopicRegex(Pattern eventTopicRegex) { 67 | this.eventTopicRegex = eventTopicRegex; 68 | } 69 | 70 | @Autowired 71 | public void setConsumerGroupRegex(Pattern consumerGroupRegex) { 72 | this.consumerGroupRegex = consumerGroupRegex; 73 | } 74 | 75 | private boolean shouldCollectEvent(String topic) { 76 | return topic != null && (eventTopicRegex.matcher(topic).matches() || Operation.OPERATION_EVENTS.equals(topic)); 77 | } 78 | 79 | private boolean shouldCollectConsumer(String consumer) { 80 | return consumerGroupRegex.matcher(consumer).matches(); 81 | } 82 | 83 | @Override 84 | public void registerSeekCallback(ConsumerSeekAware.ConsumerSeekCallback callback) { 85 | 86 | } 87 | 88 | @Override 89 | public void onPartitionsAssigned(Map assignments, ConsumerSeekAware.ConsumerSeekCallback callback) { 90 | assignments.keySet().forEach(topicPartition -> callback.seekToEnd(topicPartition.topic(), topicPartition.partition())); 91 | 92 | } 93 | 94 | @Override 95 | public void onIdleContainer(Map assignments, ConsumerSeekAware.ConsumerSeekCallback callback) { 96 | 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/service/ConsumerOffsetListenerContainerService.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.service; 2 | 3 | import com.hazelcast.quorum.Quorum; 4 | import io.splitet.core.api.emon.configuration.hazelcast.InMemoryFailedEvent; 5 | import io.splitet.core.api.emon.configuration.hazelcast.InMemoryRestoredEvent; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Qualifier; 9 | import org.springframework.boot.context.event.ApplicationReadyEvent; 10 | import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; 11 | import org.springframework.stereotype.Service; 12 | 13 | import javax.annotation.PreDestroy; 14 | 15 | @Service 16 | @Slf4j 17 | public class ConsumerOffsetListenerContainerService { 18 | 19 | @Autowired 20 | @Qualifier("consumerOffsetListenerContainer") 21 | private ConcurrentMessageListenerContainer consumerOffsetListenerContainer; 22 | 23 | @Autowired(required = false) 24 | private Quorum defaultQuorum; 25 | 26 | private final Object listenerManagementLock = new Object(); 27 | 28 | 29 | @org.springframework.context.event.EventListener 30 | public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { 31 | synchronized (listenerManagementLock) { 32 | if (defaultQuorum == null || defaultQuorum.isPresent()) { 33 | startListen(); 34 | } 35 | } 36 | } 37 | 38 | @org.springframework.context.event.EventListener 39 | public void onApplicationEvent(InMemoryFailedEvent inMemoryFailedEvent) { 40 | stopListen(); 41 | } 42 | 43 | @org.springframework.context.event.EventListener 44 | public void onApplicationEvent(InMemoryRestoredEvent inMemoryRestoredEvent) { 45 | startListen(); 46 | } 47 | 48 | public void startListen() { 49 | synchronized (listenerManagementLock) { 50 | if (consumerOffsetListenerContainer != null && !consumerOffsetListenerContainer.isRunning()) { 51 | consumerOffsetListenerContainer.start(); 52 | } 53 | } 54 | } 55 | 56 | @PreDestroy 57 | public void stopListen() { 58 | synchronized (listenerManagementLock) { 59 | if (consumerOffsetListenerContainer != null && consumerOffsetListenerContainer.isRunning()) { 60 | consumerOffsetListenerContainer.stop(); 61 | } 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/service/EventListenContainerService.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.service; 2 | 3 | import com.hazelcast.quorum.Quorum; 4 | import io.splitet.core.api.emon.configuration.hazelcast.InMemoryFailedEvent; 5 | import io.splitet.core.api.emon.configuration.hazelcast.InMemoryRestoredEvent; 6 | import io.splitet.core.kafka.PublishedEventWrapper; 7 | import io.splitet.core.pojos.Operation; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.annotation.Qualifier; 10 | import org.springframework.boot.context.event.ApplicationReadyEvent; 11 | import org.springframework.context.event.EventListener; 12 | import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; 13 | import org.springframework.stereotype.Service; 14 | 15 | import javax.annotation.PreDestroy; 16 | 17 | @Service 18 | public class EventListenContainerService { 19 | 20 | 21 | private final Object listenerManagementLock = new Object(); 22 | @Autowired 23 | @Qualifier("messageListenerContainer") 24 | private ConcurrentMessageListenerContainer messageListenerContainer; 25 | @Autowired 26 | @Qualifier("operationListenerContainer") 27 | private ConcurrentMessageListenerContainer operationListenerContainer; 28 | @Autowired(required = false) 29 | private Quorum defaultQuorum; 30 | private boolean started = false; 31 | 32 | public EventListenContainerService() { 33 | } 34 | 35 | @EventListener 36 | public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { 37 | synchronized (listenerManagementLock) { 38 | if (defaultQuorum == null || defaultQuorum.isPresent()) 39 | startListen(); 40 | } 41 | } 42 | 43 | @EventListener 44 | public void onApplicationEvent(InMemoryFailedEvent inMemoryFailedEvent) { 45 | stopListen(); 46 | } 47 | 48 | @EventListener 49 | public void onApplicationEvent(InMemoryRestoredEvent inMemoryRestoredEvent) { 50 | startListen(); 51 | } 52 | 53 | 54 | public boolean isRunning() { 55 | return messageListenerContainer.isRunning() && operationListenerContainer.isRunning(); 56 | } 57 | 58 | 59 | public void startListen() { 60 | synchronized (listenerManagementLock) { 61 | if (messageListenerContainer != null && !messageListenerContainer.isRunning()) { 62 | messageListenerContainer.start(); 63 | } 64 | 65 | if (operationListenerContainer != null && !operationListenerContainer.isRunning()) { 66 | operationListenerContainer.start(); 67 | } 68 | } 69 | } 70 | 71 | @PreDestroy 72 | public void stopListen() { 73 | synchronized (listenerManagementLock) { 74 | if (messageListenerContainer != null && messageListenerContainer.isRunning()) { 75 | messageListenerContainer.stop(); 76 | } 77 | 78 | if (operationListenerContainer != null && operationListenerContainer.isRunning()) { 79 | operationListenerContainer.stop(); 80 | } 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/service/EventListener.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.service; 2 | 3 | import com.hazelcast.core.IMap; 4 | import io.splitet.core.api.emon.domain.Topic; 5 | import io.splitet.core.api.emon.service.processor.EndOffsetSetter; 6 | import io.splitet.core.kafka.PublishedEventWrapper; 7 | import io.splitet.core.pojos.Operation; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.kafka.clients.consumer.ConsumerRecord; 10 | import org.apache.kafka.common.TopicPartition; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.kafka.listener.ConsumerSeekAware; 13 | import org.springframework.stereotype.Controller; 14 | 15 | import java.io.Serializable; 16 | import java.util.Map; 17 | 18 | @Controller 19 | @Slf4j 20 | public class EventListener implements EventMessageListener { 21 | 22 | public static final long TIME_TO_LIVE_SECONDS = 6000; 23 | 24 | @Autowired 25 | private IMap topicsMap; 26 | 27 | 28 | public void onEventMessage(ConsumerRecord record, PublishedEventWrapper eventWrapper) { 29 | try { 30 | String topic = record.topic(); 31 | if (topic.equals("operation-events")) { 32 | log.warn("Topic must not be operation-events"); 33 | return; 34 | } 35 | String opId = eventWrapper.getContext().getOpId(); 36 | log.info("opId:" + opId + " EventMessage -> Topic: " + topic 37 | + " - Sender: " + eventWrapper.getSender() + " - aggregateId: " + eventWrapper.getContext().getCommandContext()); 38 | 39 | topicsMap.submitToKey(topic, new EndOffsetSetter(record.partition(), record.offset() + 1)); 40 | 41 | } catch (Exception e) { 42 | log.error("Error While Handling Event:" + e.getMessage(), e); 43 | } 44 | } 45 | 46 | @Override 47 | public void onPartitionsAssigned(Map assignments, ConsumerSeekAware.ConsumerSeekCallback callback) { 48 | for (Map.Entry entry : assignments.entrySet()) { 49 | String topic = entry.getKey().topic(); 50 | topicsMap.putIfAbsent(topic, new Topic()); 51 | topicsMap.submitToKey(topic, new EndOffsetSetter(entry.getKey().partition(), entry.getValue())); 52 | } 53 | log.warn("onPartitionsAssigned:" + assignments.toString()); 54 | } 55 | 56 | public void onOperationMessage(ConsumerRecord record, Operation operation) { 57 | try { 58 | log.info("opId: " + record.key() + " OperationMessage -> Topic: " + record.topic() + " - offset:" + record.offset() + " - Sender: " 59 | + operation.getSender() + " - aggregateId: " + operation.getAggregateId() + " transactionState:" + operation.getTransactionState()); 60 | 61 | topicsMap.submitToKey(Operation.OPERATION_EVENTS, new EndOffsetSetter(record.partition(), record.offset() + 1)); 62 | } catch (Exception e) { 63 | log.error("Error While Handling Event:" + e.getMessage(), e); 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/service/EventMessageListener.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.service; 2 | 3 | import io.splitet.core.kafka.PublishedEventWrapper; 4 | import io.splitet.core.pojos.Operation; 5 | import org.apache.kafka.clients.consumer.ConsumerRebalanceListener; 6 | import org.apache.kafka.clients.consumer.ConsumerRecord; 7 | import org.apache.kafka.common.TopicPartition; 8 | import org.springframework.kafka.listener.ConsumerSeekAware; 9 | import org.springframework.kafka.listener.MessageListener; 10 | 11 | import java.io.Serializable; 12 | import java.util.Collection; 13 | import java.util.Map; 14 | 15 | public interface EventMessageListener extends MessageListener, ConsumerRebalanceListener, ConsumerSeekAware { 16 | @Override 17 | default void onMessage(ConsumerRecord data) { 18 | if (data.value() instanceof PublishedEventWrapper) 19 | onEventMessage(data, (PublishedEventWrapper) data.value()); 20 | else if (data.value() instanceof Operation) 21 | onOperationMessage(data, (Operation) data.value()); 22 | } 23 | 24 | void onOperationMessage(ConsumerRecord record, Operation operation); 25 | 26 | void onEventMessage(ConsumerRecord record, PublishedEventWrapper eventWrapper); 27 | 28 | @Override 29 | default void onPartitionsRevoked(Collection partitions) { 30 | } 31 | 32 | @Override 33 | default void onPartitionsAssigned(Collection partitions) { 34 | } 35 | 36 | @Override 37 | default void registerSeekCallback(ConsumerSeekCallback callback) { 38 | } 39 | 40 | @Override 41 | default void onPartitionsAssigned(Map assignments, ConsumerSeekCallback callback) { 42 | } 43 | 44 | @Override 45 | default void onIdleContainer(Map assignments, ConsumerSeekCallback callback) { 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/service/MultipleEventMessageListener.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.service; 2 | 3 | import io.splitet.core.kafka.PublishedEventWrapper; 4 | import io.splitet.core.pojos.Operation; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.kafka.clients.consumer.ConsumerRecord; 7 | import org.apache.kafka.common.TopicPartition; 8 | 9 | import java.io.Serializable; 10 | import java.util.Collection; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.function.Consumer; 14 | 15 | @Slf4j 16 | public class MultipleEventMessageListener implements EventMessageListener { 17 | private final List eventMessageListeners; 18 | 19 | public MultipleEventMessageListener(List eventMessageListeners) { 20 | this.eventMessageListeners = eventMessageListeners; 21 | } 22 | 23 | public void applyForEach(Consumer listenerConsumer) { 24 | eventMessageListeners.forEach(eventMessageListener -> { 25 | try { 26 | listenerConsumer.accept(eventMessageListener); 27 | } catch (Exception e) { 28 | log.error(e.getMessage(), e); 29 | } 30 | }); 31 | } 32 | 33 | @Override 34 | public void onOperationMessage(ConsumerRecord record, Operation value) { 35 | applyForEach(eventMessageListener -> eventMessageListener.onOperationMessage(record, value)); 36 | } 37 | 38 | @Override 39 | public void onEventMessage(ConsumerRecord record, PublishedEventWrapper value) { 40 | applyForEach(eventMessageListener -> eventMessageListener.onEventMessage(record, value)); 41 | } 42 | 43 | @Override 44 | public void onPartitionsRevoked(Collection partitions) { 45 | applyForEach(eventMessageListener -> eventMessageListener.onPartitionsRevoked(partitions)); 46 | } 47 | 48 | @Override 49 | public void onPartitionsAssigned(Collection partitions) { 50 | applyForEach(eventMessageListener -> eventMessageListener.onPartitionsAssigned(partitions)); 51 | } 52 | 53 | @Override 54 | public void registerSeekCallback(ConsumerSeekCallback callback) { 55 | applyForEach(eventMessageListener -> eventMessageListener.registerSeekCallback(callback)); 56 | } 57 | 58 | @Override 59 | public void onPartitionsAssigned(Map assignments, ConsumerSeekCallback callback) { 60 | applyForEach(eventMessageListener -> eventMessageListener.onPartitionsAssigned(assignments, callback)); 61 | } 62 | 63 | @Override 64 | public void onIdleContainer(Map assignments, ConsumerSeekCallback callback) { 65 | applyForEach(eventMessageListener -> eventMessageListener.onIdleContainer(assignments, callback)); 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/service/OperationExpirationListener.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.service; 2 | 3 | import com.hazelcast.core.EntryEvent; 4 | import com.hazelcast.core.IMap; 5 | import com.hazelcast.core.ITopic; 6 | import com.hazelcast.map.listener.EntryExpiredListener; 7 | import io.splitet.core.api.emon.domain.HandledEvent; 8 | import io.splitet.core.api.emon.domain.NoneHandled; 9 | import io.splitet.core.api.emon.domain.Partition; 10 | import io.splitet.core.api.emon.domain.ProducedEvent; 11 | import io.splitet.core.api.emon.domain.ServiceData; 12 | import io.splitet.core.api.emon.domain.Topic; 13 | import io.splitet.core.api.emon.domain.Topology; 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | import java.util.concurrent.TimeUnit; 17 | 18 | @Slf4j 19 | public class OperationExpirationListener implements EntryExpiredListener { 20 | private IMap operationsHistoryMap; 21 | private IMap topicsMap; 22 | private ITopic operationsTopic; 23 | 24 | public OperationExpirationListener(IMap operationsHistoryMap, IMap topicsMap, ITopic operationsTopic) { 25 | this.operationsHistoryMap = operationsHistoryMap; 26 | this.topicsMap = topicsMap; 27 | this.operationsTopic = operationsTopic; 28 | } 29 | 30 | @Override 31 | public void entryExpired(EntryEvent event) { 32 | event.getKey(); 33 | Topology topology = event.getOldValue(); 34 | try { 35 | topology.getProducedEvents().forEach(this::setLeafs); 36 | } catch (Exception ex) { 37 | log.warn("Error while trying to check Leafs:" + ex.getMessage()); 38 | } 39 | 40 | operationsTopic.publish(topology); 41 | 42 | if (!topology.isFinished()) { 43 | log.warn("Topology Doesn't Finished:" + topology.toString()); 44 | operationsHistoryMap.putIfAbsent(event.getKey(), event.getOldValue(), 1, TimeUnit.DAYS); 45 | } else { 46 | log.info("Topology OK:" + topology.toString()); 47 | operationsHistoryMap.putIfAbsent(event.getKey(), event.getOldValue(), 1, TimeUnit.HOURS); 48 | } 49 | 50 | } 51 | 52 | private void setLeafs(ProducedEvent producedEvent) { 53 | producedEvent.getListeningServices().forEach((s, iHandledEvent) -> { 54 | if (iHandledEvent instanceof HandledEvent) 55 | ((HandledEvent) iHandledEvent).getProducedEvents().forEach(this::setLeafs); 56 | else if (iHandledEvent instanceof NoneHandled) { 57 | ServiceData serviceData = topicsMap.get(producedEvent.getTopic()).getServiceDataHashMap().get(s); 58 | Partition eventPartition = producedEvent.getPartition(); 59 | ((NoneHandled) iHandledEvent).setFinishedAsLeaf( 60 | serviceData.getPartitions().get(eventPartition.getNumber()) != null 61 | && serviceData.getPartitions().get(eventPartition.getNumber()).getNumber() >= eventPartition.getOffset() 62 | ); 63 | } 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/service/processor/ConsumerOffsetChangeEntryProcessor.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.service.processor; 2 | 3 | import com.hazelcast.map.AbstractEntryProcessor; 4 | import io.splitet.core.api.emon.domain.Partition; 5 | import io.splitet.core.api.emon.domain.ServiceData; 6 | import io.splitet.core.api.emon.domain.Topic; 7 | 8 | import java.util.Collections; 9 | import java.util.Map; 10 | 11 | public class ConsumerOffsetChangeEntryProcessor extends AbstractEntryProcessor { 12 | 13 | private static final long serialVersionUID = -8499685451383136540L; 14 | 15 | private final String group; 16 | private final int partitionNo; 17 | private final long offset; 18 | 19 | public ConsumerOffsetChangeEntryProcessor(String group, int partitionNo, long offset) { 20 | this.group = group; 21 | this.partitionNo = partitionNo; 22 | this.offset = offset; 23 | } 24 | 25 | @Override 26 | public Partition process(Map.Entry entry) { 27 | Topic topic = entry.getValue(); 28 | if (topic == null) { 29 | Topic newTopic = Topic.createTopic(group, partitionNo, offset); 30 | entry.setValue(newTopic); 31 | return new Partition(partitionNo, 0L); 32 | } 33 | Map serviceDataHashMap = topic.getServiceDataHashMap(); 34 | ServiceData serviceData = serviceDataHashMap.get(group); 35 | if (serviceData == null) { 36 | serviceDataHashMap.put(group, ServiceData.createServiceData(group, Collections.singletonList(new Partition(partitionNo, offset)))); 37 | entry.setValue(topic); 38 | return new Partition(partitionNo, 0L); 39 | } 40 | Partition partition = serviceData.getPartition(partitionNo); 41 | if (partition == null || partition.getOffset() < offset) { 42 | serviceData.setPartition(new Partition(partitionNo, offset)); 43 | entry.setValue(topic); 44 | return partition != null ? partition : new Partition(partitionNo, 0L); 45 | } 46 | 47 | return null; // nothing changed 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /emon-lib/src/main/java/io/splitet/core/api/emon/service/processor/EndOffsetSetter.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.service.processor; 2 | 3 | import com.hazelcast.map.AbstractEntryProcessor; 4 | import io.splitet.core.api.emon.domain.Partition; 5 | import io.splitet.core.api.emon.domain.Topic; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.util.Map; 9 | 10 | @Slf4j 11 | public class EndOffsetSetter extends AbstractEntryProcessor { 12 | 13 | private static final long serialVersionUID = 1754073597874826572L; 14 | 15 | private final int partitionNo; 16 | private final long offset; 17 | 18 | public EndOffsetSetter(int partitionNo, long offset) { 19 | this.partitionNo = partitionNo; 20 | this.offset = offset; 21 | } 22 | 23 | @Override 24 | public Object process(Map.Entry entry) { 25 | Topic topic = entry.getValue(); 26 | if (topic == null) { 27 | log.warn("Null Topic Registration in EndOffsetSetter" + entry.getKey()); 28 | topic = new Topic(); 29 | } 30 | topic.getPartitions().compute(partitionNo, (integer, partition) -> { 31 | if (partition == null) { 32 | return new Partition(partitionNo, offset); 33 | } else { 34 | partition.setOffset(Math.max(offset, partition.getOffset())); 35 | } 36 | return partition; 37 | }); 38 | entry.setValue(topic); 39 | return entry; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /emon-lib/src/test/java/io/splitet/core/pojos/OperationTest.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.pojos; 2 | 3 | import com.hazelcast.core.Hazelcast; 4 | import com.hazelcast.core.HazelcastInstance; 5 | import com.hazelcast.core.IMap; 6 | import com.hazelcast.map.EntryBackupProcessor; 7 | import com.hazelcast.map.EntryProcessor; 8 | import org.junit.Test; 9 | 10 | import java.util.Map; 11 | import java.util.regex.Pattern; 12 | 13 | /** 14 | * Created by zeldalozdemir on 26/01/2017. 15 | */ 16 | public class OperationTest { 17 | @Test 18 | public void testReadWriteExternal() throws Exception { 19 | HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(); 20 | IMap test = hazelcastInstance.getMap("test"); 21 | test.executeOnKey("123", new EntryProcessor() { 22 | @Override 23 | public Object process(Map.Entry entry) { 24 | entry.setValue("Blabla"); 25 | return entry; 26 | } 27 | 28 | @Override 29 | public EntryBackupProcessor getBackupProcessor() { 30 | return null; 31 | } 32 | }); 33 | 34 | System.out.println(test.get("123")); 35 | 36 | 37 | } 38 | 39 | @Test 40 | public void regexpTest() { 41 | Pattern compile = Pattern.compile("^(.+Event|operation-events)$"); 42 | assert compile.matcher("BalBlaEvent").matches(); 43 | assert !compile.matcher("BalBlaEvent2as").matches(); 44 | assert !compile.matcher("Event").matches(); 45 | assert compile.matcher("operation-events").matches(); 46 | assert !compile.matcher("operation").matches(); 47 | assert !compile.matcher("events").matches(); 48 | assert !compile.matcher("blablaevents").matches(); 49 | } 50 | 51 | @Test 52 | public void regexpTest2() { 53 | Pattern compile = Pattern.compile("^(.+command-query|.+-command)$"); 54 | assert compile.matcher("payment3d-process-command").matches(); 55 | 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /emon/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM frolvlad/alpine-oraclejdk8:slim 2 | MAINTAINER maaydin 3 | EXPOSE 8080 4 | ADD target/eventapis.jar / 5 | CMD java -jar eventapis.jar -------------------------------------------------------------------------------- /emon/README.md: -------------------------------------------------------------------------------- 1 | # Eventapis Store 2 | An event database to enable event sourcing and distributed transactions for applications that use the microservice architecture. 3 | 4 | # Queues 5 | * Queue1 (id-QueueName: AGGREGATE_PARENT, Example: ORDER, ) 6 | * Events: 7 | * id: UUID 8 | * EventName: ORDER_CREATED (Ex: Order Created) 9 | * EventType: EXECUTE|FAIL 10 | * Params (Event Specific Data) 11 | * Transaction Ref (?) 12 | 13 | 14 | 15 | # Aggregate 16 | * Name: AGGREGATE_NAME (ex: SEND_PAYMENT) 17 | * Related Events 18 | * Execute Events (Ex: ORDER_CREATED|EXECUTE) 19 | * Fail Events (Ex: ORDER_CREATED|FAIL) 20 | 21 | 22 | # Transaction 23 | * id: UUID 24 | * StarterAggregate: Aggregate 25 | * Events 26 | * Event1 27 | * Event2 ... 28 | * State -------------------------------------------------------------------------------- /emon/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | challenge: 4 | image: 'kloiasoft/eventapis:latest' -------------------------------------------------------------------------------- /emon/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.splitet.core 9 | splitet-parent 10 | 0.8.0-SNAPSHOT 11 | 12 | 13 | emon 14 | 15 | Eventapis Store and Monitoring 16 | 17 | 18 | 3.9.4 19 | 2.11.0 20 | 21 | 22 | 23 | 24 | com.hazelcast 25 | hazelcast 26 | ${hazelcast.version} 27 | 28 | 29 | com.hazelcast 30 | hazelcast-spring 31 | ${hazelcast.version} 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-dependencies 37 | ${spring.version} 38 | pom 39 | import 40 | 41 | 42 | org.springframework.cloud 43 | spring-cloud-dependencies 44 | ${spring.cloud.version} 45 | pom 46 | import 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | io.splitet.core 55 | emon-lib 56 | ${project.version} 57 | 58 | 59 | 60 | org.projectlombok 61 | lombok 62 | provided 63 | 64 | 65 | commons-io 66 | commons-io 67 | ${commons-io.version} 68 | 69 | 70 | org.springframework.data 71 | spring-data-commons 72 | 73 | 74 | org.springframework.boot 75 | spring-boot-starter-web 76 | 77 | 78 | org.springframework.cloud 79 | spring-cloud-starter-config 80 | 81 | 82 | org.springframework.kafka 83 | spring-kafka 84 | ${spring-kafka.version} 85 | 86 | 87 | org.springframework.boot 88 | spring-boot-starter-test 89 | test 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | src/main/resources 100 | true 101 | 102 | bootstrap.yml 103 | 104 | 105 | 106 | src/main/resources 107 | false 108 | 109 | **/* 110 | 111 | 112 | 113 | 114 | 115 | org.springframework.boot 116 | spring-boot-maven-plugin 117 | ${spring.version} 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /emon/src/main/java/io/splitet/core/api/emon/EmonApplication.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | /** 8 | * Created by mali on 20/01/2017. 9 | */ 10 | @Slf4j 11 | @SpringBootApplication 12 | 13 | public class EmonApplication { 14 | 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(EmonApplication.class, args); 18 | 19 | } 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /emon/src/main/java/io/splitet/core/api/emon/controller/CommandController.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.controller; 2 | 3 | 4 | import com.hazelcast.core.IMap; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | /** 17 | * Created by zeldalozdemir on 04/2020. 18 | */ 19 | @Slf4j 20 | @RestController 21 | @RequestMapping( 22 | value = CommandController.ENDPOINT 23 | ) 24 | public class CommandController { 25 | 26 | static final String ENDPOINT = "/commands"; 27 | 28 | @Autowired 29 | private IMap commandsMap; 30 | 31 | 32 | @GetMapping 33 | public ResponseEntity>> getTopics() { 34 | try { 35 | Set> entries = commandsMap.entrySet(); 36 | return new ResponseEntity<>(entries, HttpStatus.OK); 37 | } catch (Exception e) { 38 | log.error(e.getMessage(), e); 39 | return ResponseEntity.status(500).build(); 40 | } 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /emon/src/main/java/io/splitet/core/api/emon/controller/EventController.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.controller; 2 | 3 | 4 | import com.hazelcast.core.IMap; 5 | 6 | import io.splitet.core.api.emon.domain.ServiceData; 7 | import io.splitet.core.api.emon.domain.Topic; 8 | import io.splitet.core.api.emon.dto.TopicResponseDto; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.web.bind.annotation.GetMapping; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RestController; 17 | 18 | import java.util.AbstractMap; 19 | import java.util.Map; 20 | import java.util.stream.Collectors; 21 | 22 | /** 23 | * Created by zeldalozdemir on 04/2020. 24 | */ 25 | @Slf4j 26 | @RestController 27 | @RequestMapping( 28 | value = EventController.ENDPOINT 29 | ) 30 | public class EventController { 31 | 32 | static final String ENDPOINT = "/events"; 33 | 34 | @Autowired 35 | private IMap topicsMap; 36 | 37 | 38 | @GetMapping 39 | public ResponseEntity> getEvents() { 40 | try { 41 | Map result = topicsMap.entrySet().stream().collect(Collectors.toMap( 42 | Map.Entry::getKey, o -> new TopicResponseDto(o.getValue().getServiceDataHashMap(), o.getValue().getPartitions()) 43 | )); 44 | return new ResponseEntity<>(result, HttpStatus.OK); 45 | } catch (Exception e) { 46 | log.error(e.getMessage(), e); 47 | return ResponseEntity.status(500).build(); 48 | } 49 | } 50 | 51 | @GetMapping(value = "lag") 52 | public ResponseEntity> getLaggedTopics() { 53 | try { 54 | Map result = topicsMap.entrySet().stream() 55 | .map(entry -> new AbstractMap.SimpleEntry<>( 56 | entry.getKey(), 57 | new TopicResponseDto( 58 | entry.getValue() 59 | .getServiceDataHashMap() 60 | .entrySet().stream() 61 | .map(serviceDataEntry -> new AbstractMap.SimpleEntry<>( 62 | serviceDataEntry.getKey(), 63 | new ServiceData( 64 | serviceDataEntry.getKey(), 65 | serviceDataEntry.getValue().getPartitions().values() 66 | .stream().filter(partition -> partition.getLag() != null && partition.getLag() > 0L) 67 | .collect(Collectors.toList()) 68 | )) 69 | 70 | ) 71 | .filter(serviceData -> serviceData.getValue().getPartitions().size() > 0) 72 | .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue)), 73 | entry.getValue().getPartitions() 74 | ) 75 | ) 76 | ) 77 | .filter(topicsDtpEntry -> topicsDtpEntry.getValue().getServiceDataHashMap().size() > 0L) 78 | .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue)); 79 | 80 | return new ResponseEntity<>(result, HttpStatus.OK); 81 | } catch (Exception e) { 82 | log.error(e.getMessage(), e); 83 | return ResponseEntity.status(500).build(); 84 | } 85 | } 86 | 87 | 88 | @GetMapping(value = "{topic}") 89 | public ResponseEntity getTopic(@PathVariable("topic") String topic) { 90 | try { 91 | Topic found = topicsMap.get(topic); 92 | TopicResponseDto result = new TopicResponseDto(found.getServiceDataHashMap(), found.getPartitions()); 93 | return new ResponseEntity<>(result, HttpStatus.OK); 94 | } catch (Exception e) { 95 | log.error(e.getMessage(), e); 96 | return ResponseEntity.status(500).build(); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /emon/src/main/java/io/splitet/core/api/emon/controller/OperationController.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.controller; 2 | 3 | import com.hazelcast.core.IMap; 4 | import com.hazelcast.query.PagingPredicate; 5 | import io.splitet.core.api.emon.domain.Topic; 6 | import io.splitet.core.api.emon.domain.Topology; 7 | import io.splitet.core.api.emon.service.OperationsBroadcaster; 8 | import io.splitet.core.common.OperationContext; 9 | import io.splitet.core.exception.EventStoreException; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.data.domain.Pageable; 13 | import org.springframework.data.web.PageableDefault; 14 | import org.springframework.http.HttpStatus; 15 | import org.springframework.http.ResponseEntity; 16 | import org.springframework.web.bind.annotation.GetMapping; 17 | import org.springframework.web.bind.annotation.PathVariable; 18 | import org.springframework.web.bind.annotation.RequestMapping; 19 | import org.springframework.web.bind.annotation.RequestMethod; 20 | import org.springframework.web.bind.annotation.RestController; 21 | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; 22 | 23 | import java.io.IOException; 24 | import java.io.Serializable; 25 | import java.util.Collection; 26 | import java.util.Comparator; 27 | import java.util.Map; 28 | 29 | /** 30 | * Created by zeldalozdemir on 22/01/2017. 31 | */ 32 | @Slf4j 33 | @RestController 34 | @RequestMapping(value = "/operations") 35 | public class OperationController { 36 | 37 | @Autowired 38 | private IMap operationsMap; 39 | @Autowired 40 | private IMap operationsHistoryMap; 41 | @Autowired 42 | private IMap topicsMap; 43 | 44 | @Autowired 45 | OperationsBroadcaster operationsBroadcaster; 46 | 47 | @RequestMapping(value = "/{opId}", method = RequestMethod.GET) 48 | public ResponseEntity getOperation(@PathVariable(OperationContext.OP_ID) String opId) throws IOException, EventStoreException { 49 | Topology topology = operationsMap.get(opId); 50 | if (topology == null) { 51 | topology = operationsHistoryMap.get(opId); 52 | } 53 | if (topology == null) 54 | return ResponseEntity.notFound().build(); 55 | return new ResponseEntity(topology, HttpStatus.OK); 56 | } 57 | 58 | @RequestMapping(method = RequestMethod.GET) 59 | public ResponseEntity> getOperations(@PageableDefault Pageable pageable) throws IOException, EventStoreException { 60 | try { 61 | Collection values = operationsMap.values( 62 | new PagingPredicate<>((Comparator> & Serializable) (o1, o2) -> -1 * Long.compare(o1.getValue().getStartTime(), o2.getValue().getStartTime()), 63 | pageable.getPageSize())); 64 | return new ResponseEntity<>(values, HttpStatus.OK); 65 | } catch (Exception e) { 66 | log.error(e.getMessage(), e); 67 | return ResponseEntity.status(500).build(); 68 | } 69 | } 70 | 71 | @GetMapping(value = "/listen") 72 | public SseEmitter listenOperations() throws IOException, EventStoreException { 73 | SseEmitter sseEmitter = new SseEmitter(Long.MAX_VALUE); 74 | 75 | operationsBroadcaster.registerEmitter(sseEmitter); 76 | 77 | sseEmitter.onCompletion(() -> { 78 | log.info("SseEmitter is completed"); 79 | operationsBroadcaster.deregisterEmitter(sseEmitter); 80 | }); 81 | 82 | sseEmitter.onTimeout(() -> { 83 | log.info("SseEmitter is timed out"); 84 | operationsBroadcaster.deregisterEmitter(sseEmitter); 85 | }); 86 | 87 | sseEmitter.onError(ex -> { 88 | log.info("SseEmitter got error:", ex); 89 | operationsBroadcaster.deregisterEmitter(sseEmitter); 90 | }); 91 | log.info("Returning SSE Emitter"); 92 | return sseEmitter; 93 | } 94 | 95 | @RequestMapping(value = "/history", method = RequestMethod.GET) 96 | public ResponseEntity> getTopicsHistory(@PageableDefault Pageable pageable) { 97 | try { 98 | return new ResponseEntity<>(operationsHistoryMap.values(), HttpStatus.OK); 99 | } catch (Exception e) { 100 | log.error(e.getMessage(), e); 101 | return ResponseEntity.status(500).build(); 102 | } 103 | } 104 | 105 | 106 | } -------------------------------------------------------------------------------- /emon/src/main/java/io/splitet/core/api/emon/controller/ServiceController.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.controller; 2 | 3 | 4 | import com.hazelcast.core.IMap; 5 | import io.splitet.core.api.emon.domain.ServiceData; 6 | import io.splitet.core.api.emon.domain.Topic; 7 | import io.splitet.core.api.emon.dto.ServiceResponseDto; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | import java.util.Objects; 20 | import java.util.Set; 21 | 22 | /** 23 | * Created by zeldalozdemir on 04/2020. 24 | */ 25 | @Slf4j 26 | @RestController 27 | @RequestMapping( 28 | value = ServiceController.ENDPOINT 29 | ) 30 | public class ServiceController { 31 | 32 | static final String ENDPOINT = "/services"; 33 | 34 | @Autowired 35 | private IMap topicsMap; 36 | 37 | @Autowired 38 | private IMap commandsMap; 39 | 40 | 41 | @GetMapping 42 | public ResponseEntity> getServices() { 43 | try { 44 | Map responseDtoHashMap = new HashMap<>(); 45 | Set> entries = topicsMap.entrySet(); 46 | entries 47 | .forEach(topicEntry -> { 48 | for (Map.Entry serviceDataEntry : topicEntry.getValue().getServiceDataHashMap().entrySet()) { 49 | if (!responseDtoHashMap.containsKey(serviceDataEntry.getKey())) { 50 | responseDtoHashMap.put(serviceDataEntry.getKey(), new ServiceResponseDto()); 51 | } 52 | responseDtoHashMap.get(serviceDataEntry.getKey()).addTopicPartitions(topicEntry.getKey(), serviceDataEntry.getValue().getPartitions()); 53 | } 54 | }); 55 | commandsMap.entrySet().forEach(entry -> { 56 | responseDtoHashMap.get(entry.getValue()).addCommand(entry.getKey()); 57 | }); 58 | return new ResponseEntity<>(responseDtoHashMap, HttpStatus.OK); 59 | 60 | } catch (Exception e) { 61 | log.error(e.getMessage(), e); 62 | return ResponseEntity.status(500).build(); 63 | } 64 | } 65 | 66 | 67 | @GetMapping(value = "{serviceName}") 68 | public ResponseEntity getService(@PathVariable("serviceName") String serviceName) { 69 | try { 70 | ServiceResponseDto serviceResponseDto = new ServiceResponseDto(); 71 | Set> entries = topicsMap.entrySet(); 72 | entries 73 | .forEach(topicEntry -> { 74 | for (Map.Entry serviceDataEntry : topicEntry.getValue().getServiceDataHashMap().entrySet()) { 75 | if (Objects.equals(serviceName, serviceDataEntry.getKey())) { 76 | serviceResponseDto.addTopicPartitions(topicEntry.getKey(), serviceDataEntry.getValue().getPartitions()); 77 | } 78 | } 79 | }); 80 | 81 | commandsMap.entrySet().stream().filter(entry -> Objects.equals(entry.getValue(), serviceName)) 82 | .map(Map.Entry::getKey) 83 | .forEach(serviceResponseDto::addCommand); 84 | return new ResponseEntity<>(serviceResponseDto, HttpStatus.OK); 85 | 86 | } catch (Exception e) { 87 | log.error(e.getMessage(), e); 88 | return ResponseEntity.status(500).build(); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /emon/src/main/java/io/splitet/core/api/emon/dto/ResponseDto.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.dto; 2 | 3 | import io.splitet.core.api.emon.domain.Topology; 4 | import lombok.Data; 5 | 6 | import java.util.Map; 7 | import java.util.Set; 8 | 9 | @Data 10 | public class ResponseDto { 11 | private Set> operations; 12 | } 13 | -------------------------------------------------------------------------------- /emon/src/main/java/io/splitet/core/api/emon/dto/ServiceResponseDto.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.dto; 2 | 3 | import io.splitet.core.api.emon.domain.Partition; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.HashMap; 9 | import java.util.HashSet; 10 | import java.util.Map; 11 | import java.util.Set; 12 | 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class ServiceResponseDto { 17 | 18 | private Map> topicPartitions = new HashMap<>(); 19 | private Set commands = new HashSet<>(); 20 | 21 | public void addTopicPartitions(String topic, Map partitionMap) { 22 | topicPartitions.put(topic, partitionMap); 23 | } 24 | 25 | public void addCommand(String value) { 26 | commands.add(value); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /emon/src/main/java/io/splitet/core/api/emon/dto/TopicResponseDto.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import io.splitet.core.api.emon.domain.Partition; 6 | import io.splitet.core.api.emon.domain.ServiceData; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.stream.Collectors; 14 | 15 | @Data 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class TopicResponseDto { 19 | 20 | @JsonIgnore 21 | private Map serviceDataHashMap = new HashMap<>(); 22 | 23 | @JsonIgnore 24 | private Map partitionsMap = new HashMap<>(); 25 | 26 | public Map> getServiceData() { 27 | return getServiceDataHashMap().entrySet().stream().collect( 28 | Collectors.toMap(Map.Entry::getKey, 29 | o -> o.getValue().getPartitions().entrySet().stream().collect( 30 | Collectors.toMap(Map.Entry::getKey, p -> { 31 | if (p.getValue().getLag() != null && p.getValue().getLag() > 0L) { 32 | return p.getValue().getOffset() + "(lag=" + p.getValue().getLag() + ")"; 33 | } else { 34 | return p.getValue().getOffset().toString(); 35 | } 36 | })) 37 | ) 38 | ); 39 | 40 | } 41 | 42 | @JsonProperty("partitions") 43 | public Map getPartitions() { 44 | return partitionsMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, o -> o.getValue().getOffset())); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /emon/src/main/java/io/splitet/core/api/emon/service/OperationsBroadcaster.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.emon.service; 2 | 3 | import com.hazelcast.core.ITopic; 4 | import com.hazelcast.core.Message; 5 | import com.hazelcast.core.MessageListener; 6 | import io.splitet.core.api.emon.domain.Topology; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; 11 | 12 | import javax.annotation.PostConstruct; 13 | import java.util.ArrayList; 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | @Service 18 | @Slf4j 19 | public class OperationsBroadcaster implements MessageListener { 20 | 21 | @Autowired 22 | private ITopic operationsTopic; 23 | 24 | private final List emitters = Collections.synchronizedList(new ArrayList<>()); 25 | 26 | @PostConstruct 27 | private void postConstruct() { 28 | operationsTopic.addMessageListener(this); 29 | } 30 | 31 | 32 | public void registerEmitter(SseEmitter sseEmitter) { 33 | emitters.add(sseEmitter); 34 | } 35 | 36 | public void deregisterEmitter(SseEmitter sseEmitter) { 37 | emitters.remove(sseEmitter); 38 | } 39 | 40 | @Override 41 | public void onMessage(Message message) { 42 | emitters.removeIf(sseEmitter -> { 43 | try { 44 | sseEmitter.send(message.getMessageObject()); 45 | return false; 46 | } catch (Exception e) { 47 | log.error(e.getMessage(), e); 48 | return true; 49 | } 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /emon/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 7800 3 | jetty: 4 | acceptors: 2 5 | service: 6 | name: Eventapis Operation Center 7 | 8 | eventapis: 9 | eventBus: 10 | bootstrapServers: localhost:9092 11 | zookeeperServers: localhost:2181 12 | consumer: 13 | groupId: ${info.build.artifact} 14 | consumerGroupRegex: "^(.+-service)$" 15 | 16 | emon: 17 | hazelcast: 18 | discovery: 19 | type: multicast 20 | multicast: 21 | multicastGroup: 224.0.0.1 22 | multicastPort: 54327 23 | multicastTimeToLive: 15 24 | multicastTimeoutSeconds: 5 25 | # trustedInterfaces: localhost,127.0.0.1 26 | loopbackModeEnabled: true 27 | interfaces: 28 | enabled: true 29 | interfaces: 127.0.0.1 30 | group: 31 | name: emon 32 | password: emon123 33 | listenTopology: 34 | enabled: true 35 | offsetScheduler: 36 | enabled: true 37 | 38 | -------------------------------------------------------------------------------- /emon/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: ${project.artifactId} 4 | hateoas: 5 | use-hal-as-default-json-media-type: false 6 | 7 | info: 8 | build: 9 | artifact: ${project.artifactId} 10 | name: ${project.name} 11 | description: ${project.description} 12 | version: ${project.version} -------------------------------------------------------------------------------- /emon/src/test/java/io/splitet/core/pojos/OperationTest.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.pojos; 2 | 3 | import org.junit.Test; 4 | 5 | /** 6 | * Created by zeldalozdemir on 26/01/2017. 7 | */ 8 | public class OperationTest { 9 | @Test 10 | public void testReadWriteExternal() throws Exception { 11 | 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /java-api/README.md: -------------------------------------------------------------------------------- 1 | * Aggregate Style (Transaction Definition) 2 | * Factory.command(Parameters,EventImplementation,[FailLogic]). 3 | * events(EventCommand,....). 4 | * after(). 5 | * events(EventCommand,....). 6 | * build() 7 | 8 | 9 | * Event Register (Microservice startups) 10 | * Eventstore.register(EventImplementation, [FailLogic]) 11 | 12 | * EventCommand 13 | * name (Microservice Name ?) 14 | * Parameters 15 | 16 | * EventImplementation 17 | * name 18 | * execute (Parameters) 19 | 20 | * FailLogic 21 | * Automatic 22 | * Custom 23 | * Execute (transaction, cause,) -------------------------------------------------------------------------------- /java-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.splitet.core 9 | splitet-parent 10 | 0.8.0-SNAPSHOT 11 | 12 | 13 | java-api 14 | 15 | 16 | 2.11.0 17 | 4.4 18 | 3.9 19 | 29.0-jre 20 | 1.9.5 21 | 1.3 22 | 23 | 24 | 25 | 26 | org.projectlombok 27 | lombok 28 | 29 | 30 | com.datastax.cassandra 31 | cassandra-driver-core 32 | ${cassandra-driver.version} 33 | 34 | 35 | org.slf4j 36 | slf4j-api 37 | 38 | 39 | commons-io 40 | commons-io 41 | ${commons-io.version} 42 | 43 | 44 | org.apache.commons 45 | commons-collections4 46 | ${commons-collections4.version} 47 | 48 | 49 | org.apache.commons 50 | commons-lang3 51 | ${commons-lang3.version} 52 | 53 | 54 | com.fasterxml.jackson.core 55 | jackson-databind 56 | 57 | 58 | org.apache.kafka 59 | kafka-clients 60 | ${kafka-clients.version} 61 | 62 | 63 | pl.touk 64 | throwing-function 65 | ${throwing-function.version} 66 | 67 | 68 | com.google.guava 69 | guava 70 | ${guava.version} 71 | 72 | 73 | org.aspectj 74 | aspectjweaver 75 | ${aspectjweaver.version} 76 | 77 | 78 | org.mockito 79 | mockito-all 80 | 81 | 82 | junit 83 | junit 84 | 85 | 86 | org.hamcrest 87 | hamcrest-all 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/TestMain.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | /** 6 | * Created by zeldalozdemir on 30/01/2017. 7 | */ 8 | @Slf4j 9 | public class TestMain { 10 | public static void main(String[] args) { 11 | 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/api/Command.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api; 2 | 3 | import com.fasterxml.jackson.annotation.JacksonAnnotation; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Command Entry Methods. 12 | */ 13 | @Target(ElementType.METHOD) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @JacksonAnnotation 16 | public @interface Command { 17 | long commandTimeout() default CommandHandler.DEFAULT_COMMAND_TIMEOUT; 18 | 19 | String eventRepository() default "eventRepository"; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/api/CommandDto.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api; 2 | 3 | public interface CommandDto { 4 | } 5 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/api/CommandHandler.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api; 2 | 3 | /** 4 | * Created by zeldalozdemir on 21/04/2017. 5 | */ 6 | @SuppressWarnings("checkstyle:interfaceistype") 7 | public interface CommandHandler { 8 | long DEFAULT_COMMAND_TIMEOUT = 10000L; 9 | } 10 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/api/EventHandler.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api; 2 | 3 | import io.splitet.core.common.ReceivedEvent; 4 | 5 | /** 6 | * Created by zeldalozdemir on 21/04/2017. 7 | */ 8 | public interface EventHandler { 9 | Object execute(D event) throws Exception; 10 | } 11 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/api/EventRepository.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api; 2 | 3 | import io.splitet.core.cassandra.ConcurrencyResolver; 4 | import io.splitet.core.cassandra.ConcurrentEventException; 5 | import io.splitet.core.cassandra.ConcurrentEventResolver; 6 | import io.splitet.core.cassandra.EntityEvent; 7 | import io.splitet.core.common.EventKey; 8 | import io.splitet.core.common.EventRecorder; 9 | import io.splitet.core.common.PublishedEvent; 10 | import io.splitet.core.exception.EventStoreException; 11 | import io.splitet.core.view.Entity; 12 | 13 | import java.util.List; 14 | import java.util.function.Function; 15 | import java.util.function.Supplier; 16 | 17 | /** 18 | * Created by zeldalozdemir on 21/04/2017. 19 | */ 20 | public interface EventRepository { 21 | 22 | List markFail(String opId); 23 | 24 |

EventKey recordAndPublish(P publishedEvent) throws EventStoreException, ConcurrentEventException; 25 | 26 |

EventKey recordAndPublish(Entity entity, P publishedEvent) throws EventStoreException, ConcurrentEventException; 27 | 28 |

EventKey recordAndPublish(EventKey eventKey, P publishedEvent) throws EventStoreException, ConcurrentEventException; 29 | 30 |

EventKey recordAndPublish( 31 | Entity entity, P publishedEvent, Function> concurrencyResolverFactory 32 | ) throws EventStoreException, T; 33 | 34 |

EventKey recordAndPublish( 35 | EventKey eventKey, P publishedEvent, Function> concurrencyResolverFactory 36 | ) throws EventStoreException, T; 37 | 38 |

EventKey recordAndPublish( 39 | Entity entity, P publishedEvent, Supplier> concurrencyResolverFactory 40 | ) throws EventStoreException, T; 41 | 42 |

EventKey recordAndPublish( 43 | EventKey eventKey, P publishedEvent, Supplier> concurrencyResolverFactory 44 | ) throws EventStoreException, T; 45 | 46 | EventRecorder getEventRecorder(); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/api/IUserContext.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api; 2 | 3 | import java.util.Map; 4 | 5 | public interface IUserContext { 6 | Map getUserContext(); 7 | 8 | void extractUserContext(Map userContext); 9 | 10 | void clearUserContext(); 11 | 12 | String getAuditInfo(); 13 | } 14 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/api/IdCreationStrategy.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api; 2 | 3 | /** 4 | * Created by zeldal on 25/05/2017. 5 | */ 6 | public interface IdCreationStrategy { 7 | String nextId(); 8 | } 9 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/api/RollbackCommandSpec.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api; 2 | 3 | import com.fasterxml.jackson.core.TreeNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.google.common.reflect.TypeToken; 6 | import io.splitet.core.common.RecordedEvent; 7 | import io.splitet.core.pojos.CommandRecord; 8 | 9 | import java.io.IOException; 10 | import java.lang.reflect.InvocationTargetException; 11 | import java.lang.reflect.Method; 12 | import java.lang.reflect.ParameterizedType; 13 | import java.util.AbstractMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * Created by zeldalozdemir on 21/02/2017. 18 | * To use this, You have to implement rollback method with same signature as CommandHandler.execute 19 | * When rollback occurs, it will trigger rollback with same parameters. 20 | */ 21 | public interface RollbackCommandSpec

extends RollbackSpec { 22 | 23 | default Map.Entry> getNameAndClass() { 24 | ParameterizedType type = (ParameterizedType) TypeToken.of(this.getClass().getGenericInterfaces()[0]).getType(); 25 | try { 26 | Class publishedEventClass = (Class) Class.forName(type.getActualTypeArguments()[0].getTypeName()); 27 | return new AbstractMap.SimpleEntry(publishedEventClass.getSimpleName(), CommandRecord.class); 28 | } catch (ClassNotFoundException e) { 29 | throw new RuntimeException(e); 30 | } 31 | } 32 | 33 | default void rollback(CommandRecord record) { 34 | ObjectMapper objectMapper = new ObjectMapper(); // Get from context 35 | for (Method method : this.getClass().getDeclaredMethods()) { 36 | if (method.getName().equals("rollback")) { 37 | if (method.getParameterCount() == 1 && method.getParameterTypes()[0] == CommandRecord.class) 38 | continue; 39 | try { 40 | Object[] args = new Object[method.getParameterCount()]; 41 | for (Map.Entry entry : record.getParameters().entrySet()) { 42 | Class type = method.getParameterTypes()[entry.getKey()]; 43 | args[entry.getKey()] = objectMapper.treeToValue((TreeNode) entry.getValue(), type); 44 | } 45 | method.invoke(this, args); 46 | } catch (InvocationTargetException | IllegalAccessException | IOException e) { 47 | throw new RuntimeException(e.getMessage(), e); 48 | } 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/api/RollbackSpec.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api; 2 | 3 | import com.google.common.reflect.TypeToken; 4 | import io.splitet.core.common.RecordedEvent; 5 | 6 | import java.lang.reflect.ParameterizedType; 7 | import java.util.AbstractMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * Created by zeldalozdemir on 21/02/2017. 12 | */ 13 | public interface RollbackSpec

{ 14 | void rollback(P event); 15 | 16 | default Map.Entry> getNameAndClass() { 17 | ParameterizedType type = (ParameterizedType) TypeToken.of(this.getClass()).getSupertype(RollbackSpec.class).getType(); 18 | try { 19 | Class publishedEventClass = (Class) Class.forName(type.getActualTypeArguments()[0].getTypeName()); 20 | return new AbstractMap.SimpleEntry<>(publishedEventClass.getSimpleName(), publishedEventClass); 21 | } catch (ClassNotFoundException e) { 22 | throw new RuntimeException(e); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/api/ViewQuery.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api; 2 | 3 | 4 | import io.splitet.core.cassandra.EntityEvent; 5 | import io.splitet.core.common.EventKey; 6 | import io.splitet.core.common.PublishedEvent; 7 | import io.splitet.core.exception.EventStoreException; 8 | import io.splitet.core.view.Entity; 9 | 10 | import javax.annotation.Nullable; 11 | import java.util.List; 12 | import java.util.function.Function; 13 | 14 | /** 15 | * Created by zeldalozdemir on 23/02/2017. 16 | */ 17 | public interface ViewQuery { 18 | E queryEntity(String entityId) throws EventStoreException; 19 | 20 | E queryEntity(String entityId, int version) throws EventStoreException; 21 | 22 | E queryEntity(EventKey eventKey) throws EventStoreException; 23 | 24 | E queryEntity(String entityId, @Nullable Integer version, E previousEntity) throws EventStoreException; 25 | 26 | List queryByOpId(String opId) throws EventStoreException; 27 | 28 | List queryByOpId(String key, Function findOne) throws EventStoreException; 29 | 30 | List queryHistory(String entityId) throws EventStoreException; 31 | 32 | EntityEvent queryEvent(String entityId, int version) throws EventStoreException; 33 | 34 | T queryEventData(String entityId, int version) throws EventStoreException; 35 | 36 | List queryEventKeysByOpId(String opId); 37 | 38 | /* List queryByField(List clauses) throws EventStoreException; 39 | 40 | List multipleQueryByField(List> clauses) throws EventStoreException;*/ 41 | 42 | } 43 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/api/Views.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api; 2 | 3 | public interface Views { 4 | interface RecordedOnly { 5 | } 6 | 7 | interface PublishedOnly { 8 | } 9 | } -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/api/impl/EmptyUserContext.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.impl; 2 | 3 | import io.splitet.core.api.IUserContext; 4 | 5 | import java.util.Map; 6 | 7 | public class EmptyUserContext implements IUserContext { 8 | 9 | @Override 10 | public Map getUserContext() { 11 | return null; 12 | } 13 | 14 | @Override 15 | public void extractUserContext(Map userContext) { 16 | 17 | } 18 | 19 | @Override 20 | public void clearUserContext() { 21 | 22 | } 23 | 24 | @Override 25 | public String getAuditInfo() { 26 | return null; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/api/impl/UUIDCreationStrategy.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.impl; 2 | 3 | import io.splitet.core.api.IdCreationStrategy; 4 | 5 | import java.util.UUID; 6 | 7 | /** 8 | * Created by zeldal on 25/05/2017. 9 | */ 10 | public class UUIDCreationStrategy implements IdCreationStrategy { 11 | @Override 12 | public String nextId() { 13 | return UUID.randomUUID().toString(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/cassandra/CassandraViewQuery.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.cassandra; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.splitet.core.view.Entity; 5 | import io.splitet.core.view.EntityFunctionSpec; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Created by zeldalozdemir on 12/02/2017. 12 | */ 13 | @Slf4j 14 | public class CassandraViewQuery extends BaseCassandraViewQuery { 15 | 16 | public CassandraViewQuery(String tableName, CassandraSession cassandraSession, ObjectMapper objectMapper, List> commandSpecs) { 17 | super(tableName, cassandraSession, objectMapper, commandSpecs); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/cassandra/ConcurrencyResolver.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.cassandra; 2 | 3 | import io.splitet.core.common.EventKey; 4 | import io.splitet.core.common.RecordedEvent; 5 | import io.splitet.core.exception.EventStoreException; 6 | import org.apache.commons.lang3.tuple.ImmutablePair; 7 | import org.apache.commons.lang3.tuple.Pair; 8 | 9 | public interface ConcurrencyResolver extends ConcurrentEventResolver { 10 | 11 | void tryMore() throws T; 12 | 13 | EventKey calculateNext(EventKey failedEventKey, int lastVersion) throws T, EventStoreException; 14 | 15 | default Pair calculateNext(RecordedEvent failedEvent, EventKey failedEventKey, int lastVersion) throws T, EventStoreException { 16 | return new ImmutablePair<>(calculateNext(failedEventKey, lastVersion), failedEvent); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/cassandra/ConcurrentEventException.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.cassandra; 2 | 3 | public class ConcurrentEventException extends Exception { 4 | 5 | public ConcurrentEventException() { 6 | } 7 | 8 | public ConcurrentEventException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/cassandra/ConcurrentEventResolver.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.cassandra; 2 | 3 | import io.splitet.core.common.EventKey; 4 | import io.splitet.core.common.RecordedEvent; 5 | import io.splitet.core.exception.EventStoreException; 6 | import org.apache.commons.lang3.tuple.Pair; 7 | 8 | public interface ConcurrentEventResolver { 9 | 10 | void tryMore() throws T; 11 | 12 | Pair calculateNext(R failedEvent, EventKey failedEventKey, int lastVersion) throws T, EventStoreException; 13 | } 14 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/cassandra/DefaultConcurrencyResolver.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.cassandra; 2 | 3 | import io.splitet.core.common.EventKey; 4 | import io.splitet.core.exception.EventStoreException; 5 | 6 | public class DefaultConcurrencyResolver implements ConcurrencyResolver { 7 | @Override 8 | public void tryMore() throws ConcurrentEventException { 9 | throw new ConcurrentEventException("Concurrent Events"); 10 | } 11 | 12 | @Override 13 | public EventKey calculateNext(EventKey eventKey, int lastVersion) throws EventStoreException, ConcurrentEventException { 14 | throw new ConcurrentEventException("Concurrent Events for:" + eventKey); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/cassandra/EntityEvent.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.cassandra; 2 | 3 | import io.splitet.core.common.EventKey; 4 | import io.splitet.core.pojos.EventState; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.Date; 10 | 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class EntityEvent { 15 | 16 | private EventKey eventKey; 17 | 18 | private String opId; 19 | 20 | private Date opDate; 21 | 22 | private String eventType; 23 | 24 | private EventState status; 25 | 26 | private String auditInfo; 27 | 28 | private String eventData; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/cassandra/EventStoreConfig.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.cassandra; 2 | 3 | import com.datastax.driver.core.ConsistencyLevel; 4 | import com.datastax.driver.core.PoolingOptions; 5 | import com.datastax.driver.core.ProtocolOptions; 6 | import com.datastax.driver.core.QueryOptions; 7 | import com.datastax.driver.core.SocketOptions; 8 | import com.datastax.driver.core.policies.LoadBalancingPolicy; 9 | import com.datastax.driver.core.policies.ReconnectionPolicy; 10 | import com.datastax.driver.core.policies.RetryPolicy; 11 | import lombok.AllArgsConstructor; 12 | import lombok.Data; 13 | import lombok.NoArgsConstructor; 14 | 15 | @Data 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class EventStoreConfig { 19 | 20 | private PoolingOptions poolingOptions = new PoolingOptions(); // default options 21 | /** 22 | * Keyspace name to use. 23 | */ 24 | private String keyspaceName; 25 | 26 | /** 27 | * Name of the Cassandra cluster. 28 | */ 29 | private String clusterName; 30 | 31 | /** 32 | * Comma-separated list of cluster node addresses. 33 | */ 34 | private String contactPoints = "localhost"; 35 | 36 | /** 37 | * Port of the Cassandra server. 38 | */ 39 | private int port = ProtocolOptions.DEFAULT_PORT; 40 | 41 | /** 42 | * Login user of the server. 43 | */ 44 | private String username; 45 | 46 | /** 47 | * Login password of the server. 48 | */ 49 | private String password; 50 | 51 | /** 52 | * Compression supported by the Cassandra binary protocol. 53 | */ 54 | private ProtocolOptions.Compression compression = ProtocolOptions.Compression.NONE; 55 | 56 | /** 57 | * Class name of the load balancing policy. 58 | */ 59 | private Class loadBalancingPolicy; 60 | 61 | /** 62 | * Queries consistency level. 63 | */ 64 | private ConsistencyLevel consistencyLevel; 65 | 66 | /** 67 | * Queries serial consistency level. 68 | */ 69 | private ConsistencyLevel serialConsistencyLevel; 70 | 71 | /** 72 | * Queries default fetch size. 73 | */ 74 | private int fetchSize = QueryOptions.DEFAULT_FETCH_SIZE; 75 | 76 | /** 77 | * Reconnection policy class. 78 | */ 79 | private Class reconnectionPolicy; 80 | 81 | /** 82 | * Class name of the retry policy. 83 | */ 84 | private Class retryPolicy; 85 | 86 | /** 87 | * Socket option: connection time out. 88 | */ 89 | private int connectTimeoutMillis = SocketOptions.DEFAULT_CONNECT_TIMEOUT_MILLIS; 90 | 91 | /** 92 | * Socket option: read time out. 93 | */ 94 | private int readTimeoutMillis = SocketOptions.DEFAULT_READ_TIMEOUT_MILLIS; 95 | 96 | /** 97 | * Schema action to take at startup. 98 | */ 99 | private String schemaAction = "none"; 100 | 101 | /** 102 | * Enable SSL support. 103 | */ 104 | private boolean ssl; 105 | 106 | } 107 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/cassandra/OptimizedCassandraViewQuery.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.cassandra; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.splitet.core.common.EventKey; 5 | import io.splitet.core.exception.EventStoreException; 6 | import io.splitet.core.view.Entity; 7 | import io.splitet.core.view.EntityFunctionSpec; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.function.Function; 13 | 14 | /** 15 | * Created by zeldalozdemir on 12/02/2017. 16 | */ 17 | @Slf4j 18 | public class OptimizedCassandraViewQuery extends BaseCassandraViewQuery { 19 | 20 | Function findOne; 21 | 22 | public OptimizedCassandraViewQuery(String tableName, CassandraSession cassandraSession, 23 | ObjectMapper objectMapper, 24 | List> commandSpecs, 25 | Function findOne) { 26 | super(tableName, cassandraSession, objectMapper, commandSpecs); 27 | this.findOne = findOne; 28 | } 29 | 30 | @Override 31 | public E queryEntity(String entityId) throws EventStoreException { 32 | E existing = findOne.apply(entityId); 33 | if (existing != null) 34 | return super.queryEntity(entityId, Integer.MAX_VALUE, existing); 35 | else 36 | return super.queryEntity(entityId); 37 | } 38 | 39 | @Override 40 | public E queryEntity(EventKey eventKey) throws EventStoreException { 41 | return this.queryEntity(eventKey.getEntityId(), eventKey.getVersion()); 42 | } 43 | 44 | @Override 45 | public E queryEntity(String entityId, int version) throws EventStoreException { 46 | E existing = findOne.apply(entityId); 47 | if (existing != null && existing.getVersion() < version) 48 | return super.queryEntity(entityId, version, existing); 49 | else 50 | return super.queryEntity(entityId, version); 51 | } 52 | 53 | @Override 54 | public List queryByOpId(String opId) throws EventStoreException { 55 | return queryByOpId(opId, findOne); 56 | } 57 | 58 | @Override 59 | public List queryByOpId(String opId, Function givenFindOne) throws EventStoreException { 60 | List eventKeys = super.queryEventKeysByOpId(opId); 61 | List entities = new ArrayList<>(); 62 | for (EventKey eventKey : eventKeys) { 63 | entities.add(queryEntity(eventKey.getEntityId())); 64 | } 65 | return entities; 66 | } 67 | 68 | @Override 69 | public List queryEventKeysByOpId(String opId) { 70 | return super.queryEventKeysByOpId(opId); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/common/Context.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.common; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.function.Consumer; 12 | 13 | @Data 14 | @NoArgsConstructor 15 | @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") 16 | public class Context implements Serializable { 17 | 18 | private static final long serialVersionUID = 7165801319573687119L; 19 | 20 | private String opId; 21 | private String parentOpId; 22 | private String commandContext; 23 | private long commandTimeout; 24 | private long startTime; 25 | 26 | @JsonIgnore 27 | private transient boolean preGenerated; 28 | @JsonIgnore 29 | private transient List> preGenerationConsumers = new ArrayList<>(); 30 | 31 | public Context(String opId) { 32 | this.opId = opId; 33 | } 34 | 35 | public void setGenerated() { 36 | preGenerated = false; 37 | preGenerationConsumers.forEach(contextConsumer -> contextConsumer.accept(this)); 38 | } 39 | 40 | // private long getExpireTime() { 41 | // return startTime + commandTimeout; 42 | // } 43 | // 44 | // public boolean isEmpty() { 45 | // return opId == null; 46 | // } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/common/EventExecutionInterceptor.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.common; 2 | 3 | 4 | import io.splitet.core.api.IUserContext; 5 | import io.splitet.core.kafka.KafkaOperationRepository; 6 | import io.splitet.core.pojos.EventState; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.aspectj.lang.annotation.AfterReturning; 9 | import org.aspectj.lang.annotation.AfterThrowing; 10 | import org.aspectj.lang.annotation.Aspect; 11 | 12 | /** 13 | * Created by zeldalozdemir on 24/04/2017. 14 | */ 15 | @Aspect 16 | @Slf4j 17 | @SuppressWarnings("checkstyle:IllegalThrows") 18 | public class EventExecutionInterceptor { 19 | 20 | private KafkaOperationRepository kafkaOperationRepository; 21 | private OperationContext operationContext; 22 | private IUserContext userContext; 23 | 24 | public EventExecutionInterceptor(KafkaOperationRepository kafkaOperationRepository, OperationContext operationContext, IUserContext userContext) { 25 | this.operationContext = operationContext; 26 | this.kafkaOperationRepository = kafkaOperationRepository; 27 | this.userContext = userContext; 28 | } 29 | 30 | /* @Before("this(EventHandler+) && execution(* execute(..)) && args(object)") 31 | public void before(JoinPoint jp, Object object) throws Throwable { 32 | String commandContext = object == null ? jp.getTarget().getClass().getSimpleName() : object.getClass().getSimpleName(); 33 | operationContext.setCommandContext(commandContext); 34 | log.debug("before method:" + (object == null ? "" : object.toString())); 35 | }*/ 36 | 37 | @AfterReturning(value = "this(io.splitet.core.api.EventHandler+) && execution(* execute(..))", returning = "retVal") 38 | public void afterReturning(Object retVal) throws Throwable { 39 | log.debug("AfterReturning:" + (retVal == null ? "" : retVal.toString())); 40 | operationContext.clearCommandContext(); 41 | userContext.clearUserContext(); 42 | } 43 | 44 | @AfterThrowing(value = "this(io.splitet.core.api.EventHandler+) && execution(* execute(..))", throwing = "exception") 45 | public void afterThrowing(Exception exception) throws Throwable { 46 | try { 47 | log.debug("afterThrowing EventHandler method:" + exception.getMessage()); 48 | kafkaOperationRepository.failOperation(operationContext.getCommandContext(), event -> event.setEventState(EventState.TXN_FAILED)); 49 | } finally { 50 | operationContext.clearCommandContext(); 51 | userContext.clearUserContext(); 52 | } 53 | } 54 | 55 | /* @Around(value = " @annotation(org.springframework.kafka.annotation.KafkaListener))") 56 | public Object aroundListen(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 57 | log.info("Event Here:"+proceedingJoinPoint.getArgs()[0].toString()); 58 | return proceedingJoinPoint.proceed(); 59 | }*/ 60 | 61 | } -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/common/EventKey.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.common; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * Created by zeldalozdemir on 13/02/2017. 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @Builder 17 | public class EventKey implements Serializable { 18 | 19 | private static final long serialVersionUID = -5751030991267102373L; 20 | 21 | private String entityId; 22 | 23 | private int version; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/common/EventRecorder.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.common; 2 | 3 | import io.splitet.core.cassandra.ConcurrencyResolver; 4 | import io.splitet.core.cassandra.ConcurrentEventResolver; 5 | import io.splitet.core.cassandra.EntityEvent; 6 | import io.splitet.core.exception.EventStoreException; 7 | import io.splitet.core.pojos.EventState; 8 | 9 | import javax.annotation.Nullable; 10 | import java.util.List; 11 | import java.util.Optional; 12 | import java.util.function.Function; 13 | import java.util.function.Supplier; 14 | 15 | /** 16 | * Created by zeldalozdemir on 23/02/2017. 17 | */ 18 | public interface EventRecorder { 19 | 20 | EventKey recordEntityEvent( 21 | RecordedEvent event, long date, 22 | Optional previousEventKey, 23 | Function> concurrencyResolverFactory 24 | ) 25 | throws EventStoreException, T; 26 | 27 | EventKey recordEntityEvent( 28 | R event, long date, 29 | Optional previousEventKey, 30 | Supplier> concurrentEventResolverSupplier) 31 | throws EventStoreException, T; 32 | 33 | List markFail(String key); 34 | 35 | String updateEvent(EventKey eventKey, @Nullable RecordedEvent newEventData, @Nullable EventState newEventState, @Nullable String newEventType) throws EventStoreException; 36 | 37 | String updateEvent(EventKey eventKey, RecordedEvent newEventData) throws EventStoreException; 38 | } 39 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/common/EventType.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.common; 2 | 3 | public enum EventType { 4 | OP_SINGLE, OP_START, EVENT, OP_SUCCESS, OP_FAIL; 5 | } 6 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/common/PublishedEvent.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.common; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonView; 5 | import io.splitet.core.api.Views; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | /** 11 | * Created by zeldalozdemir on 21/04/2017. 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public abstract class PublishedEvent extends ReceivedEvent { 17 | 18 | @JsonView(Views.PublishedOnly.class) 19 | EventKey sender; 20 | 21 | public abstract EventType getEventType(); 22 | } 23 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/common/ReceivedEvent.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.common; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * Created by zeldalozdemir on 21/04/2017. 10 | */ 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public abstract class ReceivedEvent implements RecordedEvent { 15 | 16 | private EventKey sender; 17 | 18 | private EventType eventType; 19 | 20 | public EventType getEventType() { 21 | return eventType; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/common/RecordedEvent.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.common; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | 5 | public interface RecordedEvent { 6 | @JsonIgnore 7 | default String getEventName() { 8 | return this.getClass().getSimpleName(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/exception/EventContextException.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.exception; 2 | 3 | /** 4 | * Created by zeldalozdemir on 26/04/2017. 5 | */ 6 | public class EventContextException extends Exception { 7 | public EventContextException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/exception/EventPulisherException.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.exception; 2 | 3 | /** 4 | * Created by zeldalozdemir on 21/02/2017. 5 | */ 6 | public class EventPulisherException extends Exception { 7 | public EventPulisherException() { 8 | } 9 | 10 | public EventPulisherException(String message) { 11 | super(message); 12 | } 13 | 14 | public EventPulisherException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | public EventPulisherException(Throwable cause) { 19 | super(cause); 20 | } 21 | 22 | public EventPulisherException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 23 | super(message, cause, enableSuppression, writableStackTrace); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/exception/EventStoreException.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.exception; 2 | 3 | /** 4 | * Created by zeldalozdemir on 21/02/2017. 5 | */ 6 | public class EventStoreException extends Exception { 7 | public EventStoreException() { 8 | } 9 | 10 | public EventStoreException(String message) { 11 | super(message); 12 | } 13 | 14 | public EventStoreException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | public EventStoreException(Throwable cause) { 19 | super(cause); 20 | } 21 | 22 | public EventStoreException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 23 | super(message, cause, enableSuppression, writableStackTrace); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/kafka/IOperationRepository.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.kafka; 2 | 3 | import io.splitet.core.pojos.Event; 4 | 5 | /** 6 | * Created by zeldalozdemir on 20/04/2017. 7 | */ 8 | public interface IOperationRepository { 9 | 10 | void failOperation(String eventId, SerializableConsumer action); 11 | 12 | void successOperation(String eventId, SerializableConsumer action); 13 | 14 | void publishEvent(String topic, String event, long opDate); 15 | } 16 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/kafka/JsonDeserializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.splitet.core.kafka; 18 | 19 | import com.fasterxml.jackson.databind.DeserializationFeature; 20 | import com.fasterxml.jackson.databind.MapperFeature; 21 | import com.fasterxml.jackson.databind.ObjectMapper; 22 | import com.fasterxml.jackson.databind.ObjectReader; 23 | import org.apache.kafka.common.errors.SerializationException; 24 | import org.apache.kafka.common.serialization.Deserializer; 25 | 26 | import java.io.IOException; 27 | import java.util.Arrays; 28 | import java.util.Map; 29 | 30 | /** 31 | * Generic {@link Deserializer} for receiving JSON from Kafka and return Java objects. 32 | * 33 | * @param class of the entity, representing messages 34 | * @author Igor Stepanov 35 | * @author Artem Bilan 36 | */ 37 | public class JsonDeserializer implements Deserializer { 38 | 39 | protected final ObjectMapper objectMapper; 40 | 41 | protected final Class targetType; 42 | 43 | private volatile ObjectReader reader; 44 | 45 | 46 | public JsonDeserializer(Class targetType) { 47 | this(targetType, new ObjectMapper()); 48 | this.objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false); 49 | this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 50 | } 51 | 52 | @SuppressWarnings("unchecked") 53 | public JsonDeserializer(Class targetType, ObjectMapper objectMapper) { 54 | this.objectMapper = objectMapper; 55 | this.targetType = targetType; 56 | } 57 | 58 | public void configure(Map configs, boolean isKey) { 59 | // No-op 60 | } 61 | 62 | public T deserialize(String topic, byte[] data) { 63 | if (this.reader == null) { 64 | this.reader = this.objectMapper.readerFor(this.targetType); 65 | } 66 | try { 67 | T result = null; 68 | if (data != null) { 69 | result = this.reader.readValue(data); 70 | } 71 | return result; 72 | } catch (IOException e) { 73 | throw new SerializationException("Can't deserialize data [" + Arrays.toString(data) + "] from topic [" + topic + "]", e); 74 | } 75 | } 76 | 77 | public void close() { 78 | // No-op 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/kafka/JsonSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.splitet.core.kafka; 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | import org.apache.kafka.common.errors.SerializationException; 21 | import org.apache.kafka.common.serialization.Serializer; 22 | 23 | import javax.annotation.Nonnull; 24 | import java.io.IOException; 25 | import java.util.Map; 26 | 27 | /** 28 | * Generic {@link Serializer} for sending Java objects to Kafka as JSON. 29 | * 30 | * @param class of the entity, representing messages 31 | * @author Igor Stepanov 32 | * @author Artem Bilan 33 | */ 34 | public class JsonSerializer implements Serializer { 35 | 36 | protected final ObjectMapper objectMapper; 37 | 38 | 39 | public JsonSerializer(@Nonnull ObjectMapper objectMapper) { 40 | this.objectMapper = objectMapper; 41 | } 42 | 43 | public void configure(Map configs, boolean isKey) { 44 | // No-op 45 | } 46 | 47 | public byte[] serialize(String topic, T data) { 48 | try { 49 | byte[] result = null; 50 | if (data != null) { 51 | result = this.objectMapper.writeValueAsBytes(data); 52 | } 53 | return result; 54 | } catch (IOException exception) { 55 | throw new SerializationException("Can't serialize data [" + data + "] for topic [" + topic + "]", exception); 56 | } 57 | } 58 | 59 | public void close() { 60 | // No-op 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/kafka/KafkaOperationRepository.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.kafka; 2 | 3 | import io.splitet.core.api.IUserContext; 4 | import io.splitet.core.common.OperationContext; 5 | import io.splitet.core.pojos.Event; 6 | import io.splitet.core.pojos.Operation; 7 | import io.splitet.core.pojos.TransactionState; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.kafka.clients.producer.KafkaProducer; 10 | import org.apache.kafka.clients.producer.ProducerRecord; 11 | 12 | /** 13 | * Created by zeldalozdemir on 20/04/2017. 14 | */ 15 | @Slf4j 16 | public class KafkaOperationRepository implements IOperationRepository { 17 | private OperationContext operationContext; 18 | private IUserContext userContext; 19 | private KafkaProducer operationsKafka; 20 | private KafkaProducer eventsKafka; 21 | private String senderGroupId; 22 | 23 | public KafkaOperationRepository(OperationContext operationContext, 24 | IUserContext userContext, KafkaProducer operationsKafka, 25 | KafkaProducer eventsKafka, 26 | String senderGroupId) { 27 | this.operationContext = operationContext; 28 | this.userContext = userContext; 29 | this.operationsKafka = operationsKafka; 30 | this.eventsKafka = eventsKafka; 31 | this.senderGroupId = senderGroupId; 32 | } 33 | 34 | /* private KafkaTemplate operationsKafka; 35 | private KafkaTemplate eventsKafka; 36 | 37 | @Autowired 38 | public KafkaOperationRepository(@Qualifier("operationsKafka") KafkaTemplate operationsKafka, 39 | @Qualifier("eventsKafka") KafkaTemplate eventsKafka) { 40 | this.eventsKafka = eventsKafka; 41 | this.operationsKafka = operationsKafka; 42 | }*/ 43 | 44 | @Override 45 | public void failOperation(String eventId, SerializableConsumer action) { 46 | Operation operation = new Operation(); 47 | operation.setSender(senderGroupId); 48 | operation.setAggregateId(eventId); 49 | operation.setUserContext(userContext.getUserContext()); 50 | operation.setContext(operationContext.getContext()); 51 | operation.setTransactionState(TransactionState.TXN_FAILED); 52 | operation.setOpDate(System.currentTimeMillis()); 53 | log.debug("Publishing Operation:" + operation.toString()); 54 | operationsKafka.send(new ProducerRecord<>(Operation.OPERATION_EVENTS, operationContext.getContext().getOpId(), operation)); 55 | } 56 | 57 | @Override 58 | public void successOperation(String eventId, SerializableConsumer action) { 59 | Operation operation = new Operation(); 60 | operation.setSender(senderGroupId); 61 | operation.setAggregateId(eventId); 62 | operation.setTransactionState(TransactionState.TXN_SUCCEEDED); 63 | operation.setUserContext(userContext.getUserContext()); 64 | operation.setContext(operationContext.getContext()); 65 | operation.setOpDate(System.currentTimeMillis()); 66 | log.debug("Publishing Operation:" + operation.toString()); 67 | operationsKafka.send(new ProducerRecord<>(Operation.OPERATION_EVENTS, operationContext.getContext().getOpId(), operation)); 68 | } 69 | 70 | @Override 71 | public void publishEvent(String topic, String event, long opDate) { 72 | PublishedEventWrapper publishedEventWrapper = new PublishedEventWrapper(operationContext.getContext(), event, opDate); 73 | publishedEventWrapper.setUserContext(userContext.getUserContext()); 74 | publishedEventWrapper.setSender(senderGroupId); 75 | log.debug("Publishing Topic:" + topic + " Event:" + publishedEventWrapper.toString()); 76 | eventsKafka.send(new ProducerRecord<>(topic, operationContext.getContext().getOpId(), publishedEventWrapper)); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/kafka/KafkaOperationRepositoryFactory.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.kafka; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.splitet.core.api.IUserContext; 5 | import io.splitet.core.common.OperationContext; 6 | import io.splitet.core.pojos.Operation; 7 | import org.apache.kafka.clients.producer.KafkaProducer; 8 | import org.apache.kafka.common.serialization.StringSerializer; 9 | 10 | public class KafkaOperationRepositoryFactory { 11 | private KafkaProperties kafkaProperties; 12 | private IUserContext userContext; 13 | private OperationContext operationContext; 14 | 15 | public KafkaOperationRepositoryFactory(KafkaProperties kafkaProperties, IUserContext userContext, OperationContext operationContext) { 16 | this.kafkaProperties = kafkaProperties; 17 | this.userContext = userContext; 18 | this.operationContext = operationContext; 19 | } 20 | 21 | public KafkaOperationRepository createKafkaOperationRepository(ObjectMapper objectMapper) { 22 | KafkaProducer operationsKafka = new KafkaProducer<>( 23 | kafkaProperties.buildProducerProperties(), 24 | new StringSerializer(), 25 | new JsonSerializer<>(objectMapper) 26 | ); 27 | KafkaProducer eventsKafka = new KafkaProducer<>( 28 | kafkaProperties.buildProducerProperties(), 29 | new StringSerializer(), 30 | new JsonSerializer<>(objectMapper) 31 | ); 32 | return new KafkaOperationRepository( 33 | operationContext, 34 | userContext, 35 | operationsKafka, 36 | eventsKafka, 37 | kafkaProperties.getConsumer().getGroupId() 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/kafka/PublishedEventWrapper.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.kafka; 2 | 3 | import com.fasterxml.jackson.annotation.JsonGetter; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import com.fasterxml.jackson.annotation.JsonRawValue; 6 | import com.fasterxml.jackson.annotation.JsonSetter; 7 | import com.fasterxml.jackson.databind.node.ObjectNode; 8 | import io.splitet.core.common.Context; 9 | import lombok.Data; 10 | import lombok.ToString; 11 | 12 | import java.io.Serializable; 13 | import java.util.Map; 14 | 15 | /** 16 | * Created by zeldalozdemir on 25/04/2017. 17 | */ 18 | @Data 19 | @ToString(exclude = "userContext") 20 | public class PublishedEventWrapper implements Serializable { 21 | 22 | private static final long serialVersionUID = 7950670808405003425L; 23 | 24 | @JsonIgnore 25 | private String event; 26 | private Context context; 27 | private String sender; 28 | private long opDate; 29 | private Map userContext; 30 | 31 | public PublishedEventWrapper() { 32 | } 33 | 34 | public PublishedEventWrapper(Context context, String eventData, long opDate) { 35 | this.context = context; 36 | this.event = eventData; 37 | this.opDate = opDate; 38 | } 39 | 40 | @JsonGetter 41 | @JsonRawValue 42 | public String getEvent() { 43 | return event; 44 | } 45 | 46 | @JsonSetter 47 | public void setEvent(ObjectNode event) { 48 | this.event = event.toString(); 49 | } 50 | 51 | @JsonSetter 52 | public void setEventData(String eventData) { 53 | this.event = eventData; 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/kafka/SerializableConsumer.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.kafka; 2 | 3 | /** 4 | * Created by zeldalozdemir on 22/02/2017. 5 | */ 6 | 7 | import java.io.Serializable; 8 | import java.util.function.Consumer; 9 | 10 | @FunctionalInterface 11 | public interface SerializableConsumer extends Consumer, Serializable { 12 | 13 | } -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/pojos/CommandRecord.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.pojos; 2 | 3 | import com.fasterxml.jackson.annotation.JsonGetter; 4 | import com.fasterxml.jackson.annotation.JsonSetter; 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | import io.splitet.core.common.RecordedEvent; 7 | import lombok.Data; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | @Data 13 | public class CommandRecord implements RecordedEvent { 14 | private Map parameters = new HashMap<>(); 15 | private String eventName; 16 | 17 | @JsonGetter("parameters") 18 | public Map getParameters() { 19 | return (Map) parameters; 20 | } 21 | 22 | @JsonSetter("parameters") 23 | public void setParameters(Map parameters) { 24 | this.parameters = parameters; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/pojos/Event.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.pojos; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.io.Externalizable; 8 | import java.io.IOException; 9 | import java.io.ObjectInput; 10 | import java.io.ObjectOutput; 11 | import java.util.UUID; 12 | 13 | /** 14 | * Created by zeldalozdemir on 26/01/2017. 15 | */ 16 | @Data 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | public class Event implements Externalizable, Cloneable { 20 | private UUID eventId; 21 | private IEventType eventType; 22 | private EventState eventState; 23 | private String[] params; 24 | 25 | public void writeExternal(ObjectOutput out) throws IOException { 26 | out.writeUTF(eventId.toString()); 27 | out.writeUTF(eventState.name()); 28 | } 29 | 30 | public void readExternal(ObjectInput in) throws IOException { 31 | eventId = UUID.fromString(in.readUTF()); 32 | eventState = EventState.valueOf(EventState.class, in.readUTF()); 33 | 34 | } 35 | 36 | @Override 37 | public Object clone() { 38 | try { 39 | return super.clone(); 40 | } catch (CloneNotSupportedException e) { 41 | throw new RuntimeException(e); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/pojos/EventState.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.pojos; 2 | 3 | /** 4 | * Created by zeldalozdemir on 26/01/2017. 5 | */ 6 | public enum EventState { 7 | CREATED, 8 | FAILED, 9 | SUCCEEDED, 10 | TXN_FAILED, 11 | TXN_SUCCEEDED; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/pojos/IEventType.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.pojos; 2 | 3 | /** 4 | * Created by zeldalozdemir on 30/01/2017. 5 | */ 6 | public enum IEventType { 7 | EXECUTE, 8 | ROLLBACK; 9 | /* 10 | public static final IEventType EXECUTE = new IEventType() { }; 11 | public static final IEventType ROLLBACK = new IEventType() { };*/ 12 | } 13 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/pojos/IOperationEvents.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.pojos; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Created by zeldalozdemir on 20/04/2017. 7 | */ 8 | public interface IOperationEvents extends Serializable { 9 | } 10 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/pojos/Operation.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.pojos; 2 | 3 | import io.splitet.core.common.Context; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import lombok.ToString; 8 | 9 | import java.io.Serializable; 10 | import java.util.Map; 11 | 12 | /** 13 | * Created by zeldalozdemir on 25/01/2017. 14 | */ 15 | @Data 16 | @ToString(exclude = "userContext") 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | public class Operation implements Serializable { 20 | public static final String OPERATION_EVENTS = "operation-events"; 21 | private static final long serialVersionUID = -2003849346218727591L; 22 | 23 | private TransactionState transactionState; 24 | private String aggregateId; 25 | private String sender; 26 | private String parentId; // alias for as context.getParentOpId() 27 | private Context context; 28 | private long opDate; 29 | private Map userContext; 30 | 31 | /** 32 | * Backward compatible. 33 | * 34 | * @return 35 | */ 36 | public String getParentId() { 37 | return parentId != null ? parentId : (context != null ? context.getParentOpId() : null); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/pojos/TransactionState.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.pojos; 2 | 3 | /** 4 | * Created by zeldalozdemir on 26/01/2017. 5 | */ 6 | public enum TransactionState { 7 | RUNNING, 8 | TXN_FAILED, 9 | TXN_SUCCEEDED; 10 | } 11 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/view/AggregateListener.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.view; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.splitet.core.api.EventRepository; 5 | import io.splitet.core.api.RollbackSpec; 6 | import io.splitet.core.api.ViewQuery; 7 | import io.splitet.core.cassandra.EntityEvent; 8 | import io.splitet.core.common.PublishedEvent; 9 | import io.splitet.core.common.RecordedEvent; 10 | import io.splitet.core.exception.EventStoreException; 11 | import io.splitet.core.pojos.Operation; 12 | import io.splitet.core.pojos.TransactionState; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.apache.kafka.clients.consumer.ConsumerRecord; 15 | 16 | import java.util.AbstractMap; 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | @Slf4j 22 | public class AggregateListener { 23 | final Map, RollbackSpec>> rollbackSpecMap; 24 | ViewQuery viewQuery; 25 | EventRepository eventRepository; 26 | SnapshotRepository snapshotRepository; 27 | private ObjectMapper objectMapper; 28 | 29 | public AggregateListener(ViewQuery viewQuery, 30 | EventRepository eventRepository, 31 | SnapshotRepository snapshotRepository, 32 | List rollbackSpecs, 33 | ObjectMapper objectMapper) { 34 | this.viewQuery = viewQuery; 35 | this.eventRepository = eventRepository; 36 | this.snapshotRepository = snapshotRepository; 37 | this.objectMapper = objectMapper; 38 | rollbackSpecMap = new HashMap<>(); 39 | rollbackSpecs.forEach(rollbackSpec -> { 40 | Map.Entry> entry = rollbackSpec.getNameAndClass(); 41 | rollbackSpecMap.put(entry.getKey(), new AbstractMap.SimpleEntry<>(entry.getValue(), rollbackSpec)); 42 | }); 43 | } 44 | 45 | public void listenOperations(ConsumerRecord data) throws EventStoreException { 46 | try { 47 | if (data.value().getTransactionState() == TransactionState.TXN_FAILED) { 48 | List entityEvents = eventRepository.markFail(data.key()); 49 | runRollbacks(entityEvents); 50 | snapshotRepository.saveAll(viewQuery.queryByOpId(data.key())); // We may not need this 51 | } else if (data.value().getTransactionState() == TransactionState.TXN_SUCCEEDED) { 52 | snapshotRepository.saveAll(viewQuery.queryByOpId(data.key())); 53 | } 54 | snapshotRepository.flush(); 55 | } catch (EventStoreException e) { 56 | log.error("Error while applying operation:" + data.toString() + " Exception:" + e.getMessage(), e); 57 | } 58 | } 59 | 60 | void runRollbacks(List entityEvents) { 61 | entityEvents.forEach(entityEvent -> { 62 | try { 63 | Map.Entry, RollbackSpec> specEntry = rollbackSpecMap.get(entityEvent.getEventType()); 64 | if (specEntry != null) { 65 | RecordedEvent eventData = new EntityEventWrapper<>(specEntry.getKey(), objectMapper, entityEvent).getEventData(); 66 | if (eventData instanceof PublishedEvent) { 67 | ((PublishedEvent) eventData).setSender(entityEvent.getEventKey()); 68 | } 69 | specEntry.getValue().rollback(eventData); 70 | } 71 | } catch (Exception e) { 72 | log.warn(e.getMessage(), e); 73 | } 74 | }); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/view/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.view; 2 | 3 | import io.splitet.core.common.EventKey; 4 | import lombok.Data; 5 | 6 | @Data 7 | public abstract class BaseEntity implements Entity { 8 | 9 | private String id; 10 | private int version; 11 | 12 | public BaseEntity() { 13 | } 14 | 15 | public BaseEntity(String id, int version) { 16 | this.id = id; 17 | this.version = version; 18 | } 19 | 20 | @Override 21 | public EventKey getEventKey() { 22 | return new EventKey(id, version); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/view/Entity.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.view; 2 | 3 | import io.splitet.core.common.EventKey; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * Created by zeldalozdemir on 21/02/2017. 9 | */ 10 | public interface Entity extends Serializable { 11 | 12 | EventKey getEventKey(); 13 | 14 | String getId(); 15 | 16 | void setId(String id); 17 | 18 | int getVersion(); 19 | 20 | void setVersion(int version); 21 | } 22 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/view/EntityEventWrapper.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.view; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.splitet.core.cassandra.EntityEvent; 5 | import io.splitet.core.exception.EventStoreException; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.io.IOException; 11 | 12 | /** 13 | * Created by zeldalozdemir on 07/02/2017. 14 | */ 15 | @Data 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class EntityEventWrapper { 19 | 20 | private Class type; 21 | private ObjectMapper objectMapper; 22 | private EntityEvent entityEvent; 23 | 24 | public E getEventData() throws EventStoreException { 25 | try { 26 | return objectMapper.readValue(entityEvent.getEventData(), type); 27 | } catch (IOException e) { 28 | throw new EventStoreException(e.getMessage(), e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/view/EntityFunction.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.view; 2 | 3 | import io.splitet.core.exception.EventStoreException; 4 | 5 | /** 6 | * Created by zeldalozdemir on 21/02/2017. 7 | */ 8 | @FunctionalInterface 9 | public interface EntityFunction { 10 | E apply(E previous, EntityEventWrapper event) throws EventStoreException; 11 | } -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/view/EntityFunctionSpec.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.view; 2 | 3 | import com.google.common.reflect.TypeToken; 4 | import lombok.Getter; 5 | import lombok.NonNull; 6 | 7 | import java.lang.reflect.ParameterizedType; 8 | 9 | /** 10 | * Created by zeldalozdemir on 21/02/2017. 11 | */ 12 | @Getter 13 | public abstract class EntityFunctionSpec { 14 | @Getter 15 | private final EntityFunction entityFunction; 16 | 17 | public EntityFunctionSpec(@NonNull EntityFunction entityFunction) { 18 | this.entityFunction = entityFunction; 19 | } 20 | 21 | public Class getQueryType() { 22 | ParameterizedType type = (ParameterizedType) TypeToken.of(this.getClass()).getSupertype(EntityFunctionSpec.class).getType(); 23 | try { 24 | return (Class) Class.forName(type.getActualTypeArguments()[1].getTypeName()); 25 | } catch (ClassNotFoundException e) { 26 | throw new RuntimeException(e); 27 | } 28 | } 29 | 30 | public Class getEntityType() { 31 | ParameterizedType type = (ParameterizedType) TypeToken.of(this.getClass()).getSupertype(EntityFunctionSpec.class).getType(); 32 | try { 33 | return (Class) Class.forName(type.getActualTypeArguments()[0].getTypeName()); 34 | } catch (ClassNotFoundException e) { 35 | throw new RuntimeException(e); 36 | } 37 | 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /java-api/src/main/java/io/splitet/core/view/SnapshotRepository.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.view; 2 | 3 | 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | @SuppressWarnings("checkstyle:InterfaceTypeParameterName") 8 | public interface SnapshotRepository { 9 | List saveAll(Iterable entities); 10 | 11 | S save(S entity); 12 | 13 | void flush(); 14 | 15 | Optional findById(ID id); 16 | } 17 | -------------------------------------------------------------------------------- /java-api/src/test/java/io/splitet/core/api/impl/EmptyUserContextTest.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.impl; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.mockito.InjectMocks; 6 | import org.mockito.Mockito; 7 | import org.mockito.runners.MockitoJUnitRunner; 8 | 9 | import java.util.Map; 10 | 11 | import static org.hamcrest.Matchers.nullValue; 12 | import static org.junit.Assert.assertThat; 13 | import static org.mockito.Mockito.verifyZeroInteractions; 14 | 15 | @RunWith(MockitoJUnitRunner.class) 16 | public class EmptyUserContextTest { 17 | 18 | @InjectMocks 19 | private EmptyUserContext emptyUserContext; 20 | 21 | @Test 22 | public void shouldGetUserContext() throws Exception { 23 | // Given 24 | 25 | // When 26 | Map actual = emptyUserContext.getUserContext(); 27 | 28 | // Then 29 | assertThat(actual, nullValue()); 30 | } 31 | 32 | @Test 33 | public void shouldExtractUserContext() throws Exception { 34 | // Given 35 | Map userContext = (Map) Mockito.mock(Map.class); 36 | 37 | // When 38 | emptyUserContext.extractUserContext(userContext); 39 | 40 | // Then 41 | verifyZeroInteractions(userContext); 42 | } 43 | 44 | @Test 45 | public void shouldClearUserContext() throws Exception { 46 | // Given 47 | 48 | // When 49 | emptyUserContext.clearUserContext(); 50 | 51 | // Then 52 | 53 | } 54 | 55 | @Test 56 | public void shouldGetAuditInfo() throws Exception { 57 | // Given 58 | 59 | // When 60 | String actual = emptyUserContext.getAuditInfo(); 61 | 62 | // Then 63 | assertThat(actual, nullValue()); 64 | } 65 | } -------------------------------------------------------------------------------- /java-api/src/test/java/io/splitet/core/api/impl/UUIDCreationStrategyTest.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.api.impl; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.mockito.InjectMocks; 6 | import org.mockito.runners.MockitoJUnitRunner; 7 | 8 | import java.util.UUID; 9 | 10 | import static org.hamcrest.Matchers.equalTo; 11 | import static org.junit.Assert.assertThat; 12 | 13 | @RunWith(MockitoJUnitRunner.class) 14 | public class UUIDCreationStrategyTest { 15 | 16 | @InjectMocks 17 | private UUIDCreationStrategy uUIDCreationStrategy; 18 | 19 | @Test 20 | public void shouldGetNextId() throws Exception { 21 | // Given 22 | 23 | // When 24 | String actual = uUIDCreationStrategy.nextId(); 25 | 26 | // Then 27 | UUID uuid = UUID.fromString(actual); 28 | String stringValue = uuid.toString(); 29 | assertThat(actual, equalTo(stringValue)); 30 | } 31 | } -------------------------------------------------------------------------------- /java-api/src/test/java/io/splitet/core/cassandra/DefaultConcurrencyResolverTest.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.cassandra; 2 | 3 | import io.splitet.core.common.EventKey; 4 | import org.hamcrest.Matchers; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.InjectMocks; 8 | import org.mockito.runners.MockitoJUnitRunner; 9 | 10 | import static org.hamcrest.MatcherAssert.assertThat; 11 | 12 | 13 | @RunWith(MockitoJUnitRunner.class) 14 | public class DefaultConcurrencyResolverTest { 15 | 16 | @InjectMocks 17 | private DefaultConcurrencyResolver defaultConcurrencyResolver; 18 | 19 | @Test 20 | public void testTryMore() throws Exception { 21 | try { 22 | defaultConcurrencyResolver.tryMore(); 23 | } catch (Exception e) { 24 | assertThat(e, Matchers.instanceOf(ConcurrentEventException.class)); 25 | assertThat(e.getMessage(), Matchers.containsString("Concurrent Events")); 26 | } 27 | } 28 | 29 | @Test 30 | public void testCalculateNext() throws Exception { 31 | EventKey eventKey = new EventKey("stock", 4); 32 | 33 | try { 34 | defaultConcurrencyResolver.calculateNext(eventKey, 5); 35 | } catch (Exception e) { 36 | assertThat(e, Matchers.instanceOf(ConcurrentEventException.class)); 37 | assertThat(e.getMessage(), Matchers.containsString("Concurrent Events for:")); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /java-api/src/test/java/io/splitet/core/common/OperationContextTest.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.common; 2 | 3 | import io.splitet.core.exception.EventContextException; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.junit.rules.ExpectedException; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | 11 | import static org.hamcrest.Matchers.equalTo; 12 | import static org.junit.Assert.assertNull; 13 | import static org.junit.Assert.assertThat; 14 | 15 | @RunWith(MockitoJUnitRunner.class) 16 | public class OperationContextTest { 17 | 18 | @Rule 19 | public ExpectedException expectedException = ExpectedException.none(); 20 | @InjectMocks 21 | private OperationContext operationContext; 22 | 23 | @Test 24 | public void shouldSwitchContext() { 25 | operationContext.generateContext(); 26 | operationContext.switchContext("newOpId"); 27 | 28 | String actual = operationContext.getContextOpId(); 29 | 30 | assertThat(actual, equalTo("newOpId")); 31 | } 32 | 33 | @Test 34 | public void shouldGetContext() { 35 | String context = operationContext.getContextOpId(); 36 | 37 | for (int i = 0; i < 10; i++) { 38 | String contextAgain = operationContext.getContextOpId(); 39 | assertThat(contextAgain, equalTo(context)); 40 | } 41 | } 42 | 43 | @Test 44 | public void shouldGetCommandContext() throws EventContextException { 45 | operationContext.generateContext(); 46 | operationContext.setCommandContext("123"); 47 | 48 | String actual = operationContext.getCommandContext(); 49 | 50 | assertThat(actual, equalTo("123")); 51 | } 52 | 53 | @Test 54 | public void shouldSetCommandContext() throws EventContextException { 55 | operationContext.generateContext(); 56 | operationContext.setCommandContext("123"); 57 | 58 | String actual = operationContext.getCommandContext(); 59 | 60 | assertThat(actual, equalTo("123")); 61 | } 62 | 63 | @Test 64 | public void shouldClearContext() throws EventContextException { 65 | expectedException.expect(EventContextException.class); 66 | 67 | operationContext.generateContext(); 68 | operationContext.setCommandContext("123"); 69 | operationContext.clearContext(); 70 | 71 | operationContext.setCommandContext("456"); 72 | } 73 | 74 | @Test 75 | public void shouldClearCommandContext() throws EventContextException { 76 | operationContext.generateContext(); 77 | operationContext.setCommandContext("123"); 78 | operationContext.clearCommandContext(); 79 | 80 | String actual = operationContext.getCommandContext(); 81 | 82 | assertNull(actual); 83 | } 84 | 85 | @Test 86 | public void shouldGenerateContext() { 87 | String context = operationContext.generateContext(); 88 | 89 | String actual = operationContext.getContextOpId(); 90 | 91 | assertThat(actual, equalTo(context)); 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /resources/checkstyle-suppression.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/eventapis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splitet/SplitetFramework/d75037c5e8af66e0d7eeb87135a884493a8f1458/resources/eventapis.png -------------------------------------------------------------------------------- /resources/findbugs-exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /resources/splitet_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splitet/SplitetFramework/d75037c5e8af66e0d7eeb87135a884493a8f1458/resources/splitet_logo.png -------------------------------------------------------------------------------- /spec/eventstore.md: -------------------------------------------------------------------------------- 1 | # Eventstore Specification 2 | 3 | This specification defines an Eventstore, consisting of a event pub/sub layer, event processing layer and a persistence layer. 4 | 5 | The goal of this specification is to enable the creation of interoperable tools for building an Eventstore. 6 | 7 | ## Table of Contents 8 | - [Introduction](#eventstore-specification) 9 | - [Overview](#overview) 10 | - [Events](#events) 11 | - [Event States](#event-states) 12 | - [Eventstore](#eventstore) 13 | 14 | # Overview 15 | At a high level the eventstore provides a channel to publish events and subscribe to event streams. 16 | 17 | # Events 18 | - Transaction Id 19 | - Event Id 20 | - Type 21 | - Status (New states should be handled as new events) 22 | - TTL 23 | - Data 24 | - Datetime 25 | - Initiator 26 | 27 | # Event States 28 | - Created 29 | - Failed 30 | - Succeeded 31 | - TXN Failed 32 | - TXN Succeeded 33 | 34 | # Eventstore 35 | - Publish to multi-instance of a service is optinal 36 | - Event store should handle timeouts and failures and create events automatically to trigger compansating transactions. 37 | - Transaction locks on application layer can not be handled by the eventstore, an application should handle it's own locks on case of cancel or fail events triggered for a process which is already running. 38 | - Eventstore should know the worker of an event on proccessing state. 39 | -------------------------------------------------------------------------------- /spec/samples/Sample Event Topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splitet/SplitetFramework/d75037c5e8af66e0d7eeb87135a884493a8f1458/spec/samples/Sample Event Topology.png -------------------------------------------------------------------------------- /spec/samples/sample-topology-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "34fe3a8c-0620-4c86-9704-b84b30aaa897", 3 | "handlerName": "OrderProcessCommand", 4 | "serviceName": "order-command-query-service", 5 | "snapshot-status": "DONE", 6 | "snapshotTimeInMillis": 200, 7 | "operationCompletionTimeInMillis": 1500, 8 | "events": { 9 | "OrderIsReadyForProcess": { 10 | "type": "OP_EVENT", 11 | "listeners": { 12 | "stock-command-service": { 13 | "handlerName": "OrderIsReadyForProcessHandler", 14 | "serviceName": "stock-command-service", 15 | "events": { 16 | "StockReserved": { 17 | "type": "OP_EVENT", 18 | "listeners": { 19 | "order-command-query-service": { 20 | "handlerName": "StockReservedHandler", 21 | "serviceName": "order-command-query-service", 22 | "events": { 23 | "OrderReadyForPayment": { 24 | "type": "OP_EVENT", 25 | "listeners": { 26 | "payment-command-service": { 27 | "handlerName": "OrderReadyForPaymentHandler", 28 | "serviceName": "payment-command-service", 29 | "events": { 30 | "PaymentSuccessful": { 31 | "type": "OP_EVENT", 32 | "listeners": { 33 | "order-command-query-service": { 34 | "handlerName": "PaymentSuccessfulHandler", 35 | "serviceName": "order-command-query-service", 36 | "events": { 37 | "OrderProcessedSuccessfully": { 38 | "type": "TX_SUCCESS" 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /spec/samples/sample-topology-single.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "598f430f-fe08-401c-8285-e26cf0258748", 3 | "handlerName": "OrderCreateCommand", 4 | "serviceName": "order-command-query-service", 5 | "snapshotStatus": "DONE", 6 | "completedTimeInMillis":500, 7 | "events": { 8 | "OrderCreated": { 9 | "type": "OP_SINGLE" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /spec/schema/Layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splitet/SplitetFramework/d75037c5e8af66e0d7eeb87135a884493a8f1458/spec/schema/Layers.png -------------------------------------------------------------------------------- /spec/schema/command.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://github.com/kloiasoft/eventapis/blob/master/spec/schema/command.request.json", 4 | "title": "EventApis Command Request", 5 | "description": "EventApis Command Request Schema", 6 | "type": "object", 7 | "properties": { 8 | "body": { 9 | "type": "object", 10 | "additionalProperties": true 11 | } 12 | }, 13 | "additionalProperties": true, 14 | "required": [] 15 | } -------------------------------------------------------------------------------- /spec/schema/command.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://github.com/kloiasoft/eventapis/blob/master/spec/schema/command.response.json", 4 | "title": "EventApis Command Response", 5 | "description": "EventApis Command Response Schema", 6 | "type": "object", 7 | "properties": { 8 | "headers": { 9 | "type": "object", 10 | "properties": { 11 | "X-OPID": { 12 | "type": "string", 13 | "format": "uuid", 14 | "description": "Command starts operation with this ID" 15 | }, 16 | "X-OP-TIMEOUT": { 17 | "type": "integer", 18 | "minimum": 0, 19 | "description": "Timeout in Milliseconds, Requester can assume Operation will be finished(success or fail), meaning underlying System will guarantee that it should reach consistent state after that timeout" 20 | }, 21 | "X-OP-START-TIME": { 22 | "type": "integer", 23 | "minimum": 0, 24 | "description": "Reference starting time of Operation, especially to use X-OP-TIMEOUT" 25 | } 26 | }, 27 | "required": [ 28 | "X-OPID", 29 | "X-OP-TIMEOUT", 30 | "X-OP-START-TIME" 31 | ] 32 | }, 33 | "body": { 34 | "allOf": [ 35 | { 36 | "$ref": "https://github.com/kloiasoft/eventapis/blob/master/spec/schema/event.key.json" 37 | } 38 | ], 39 | "additionalProperties": true 40 | } 41 | }, 42 | "required": [ 43 | "body" 44 | ] 45 | } -------------------------------------------------------------------------------- /spec/schema/context.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://github.com/kloiasoft/eventapis/blob/master/spec/schema/context.json", 4 | "title": "EventApis Event Context", 5 | "description": "EventApis Event Context Schema", 6 | "type": "object", 7 | "properties": { 8 | "opId": { 9 | "type": "string", 10 | "description": "Operation Id initially given while invoking command" 11 | }, 12 | "parentOpId": { 13 | "type": "string", 14 | "description": "Parent Operation Id for nested invocations" 15 | }, 16 | "commandContext": { 17 | "type": "string", 18 | "description": "Name of Initial Command" 19 | }, 20 | "commandTimeout": { 21 | "type": "integer", 22 | "description": "Timeout in Milliseconds, After this timeout Operation will be assumed as finalized" 23 | }, 24 | "startTime": { 25 | "type": "integer", 26 | "description": "Reference starting time of Operation" 27 | } 28 | }, 29 | "required": [ 30 | "opId", 31 | "commandContext", 32 | "commandTimeout", 33 | "startTime" 34 | ] 35 | } -------------------------------------------------------------------------------- /spec/schema/entity-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://github.com/kloiasoft/eventapis/blob/master/spec/schema/entity-event.json", 4 | "title": "EventApis Published Event Wrapper", 5 | "description": "EventApis Published Event Wrapper Schema", 6 | "type": "object", 7 | "properties": { 8 | "eventKey": { 9 | "$ref": "https://github.com/kloiasoft/eventapis/blob/master/spec/schema/event.key.json" 10 | }, 11 | "opId": { 12 | "type": "string", 13 | "description": "Operation Id initially given while invoking command" 14 | }, 15 | "opDate": { 16 | "type": "integer", 17 | "description": "Event Recorded Epoch" 18 | }, 19 | "eventType": { 20 | "type": "string", 21 | "description": "Type/Name of Event" 22 | }, 23 | "status": { 24 | "type": "string", 25 | "enum": [ 26 | "CREATED", 27 | "TXN_FAILED" 28 | ] 29 | }, 30 | "auditInfo": { 31 | "type": "string", 32 | "description": "Custom Audit Info for User Extension" 33 | }, 34 | "eventData": { 35 | "type": "string", 36 | "description": "Event Data as Json" 37 | } 38 | }, 39 | "required": [ 40 | "eventKey", 41 | "opId", 42 | "opDate", 43 | "eventType", 44 | "status", 45 | "eventData" 46 | ] 47 | } -------------------------------------------------------------------------------- /spec/schema/event-key.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://github.com/kloiasoft/eventapis/blob/master/spec/schema/event-key.json", 4 | "title": "EventApis Event Key", 5 | "description": "EventApis Event Key Schema", 6 | "type": "object", 7 | "properties": { 8 | "entityId": { 9 | "type": "string", 10 | "description": "Aggregation Id of related Default Entity, UUIDs can be used but not mandatory" 11 | }, 12 | "version": { 13 | "type": "integer", 14 | "minimum": 0, 15 | "description": "Aggregation Version of related Default Entity" 16 | } 17 | }, 18 | "additionalProperties": false, 19 | "required": [ 20 | "entityId", 21 | "version" 22 | ] 23 | } -------------------------------------------------------------------------------- /spec/schema/operation.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://github.com/kloiasoft/eventapis/blob/master/spec/schema/operation.json", 4 | "title": "EventApis Event Wrapper", 5 | "description": "EventApis Event Wrapper Schema", 6 | "type": "object", 7 | "properties": { 8 | "event": { 9 | "transactionState": "string", 10 | "description": "Published state of Operation", 11 | "enum": [ 12 | "RUNNING", 13 | "TXN_FAILED", 14 | "TXN_SUCCEEDED" 15 | ] 16 | }, 17 | "aggregateId": { 18 | "type": "string", 19 | "description": "Aggregate Id of Operation" 20 | }, 21 | "parentId": { 22 | "type": "string", 23 | "description": "Sender of Event - Service Name" 24 | }, 25 | "sender": { 26 | "type": "string", 27 | "description": "Sender of Event - Service Name" 28 | }, 29 | "opDate": { 30 | "type": "integer", 31 | "description": "Event Send Epoch" 32 | }, 33 | "context": { 34 | "$ref": "https://github.com/kloiasoft/eventapis/blob/master/spec/schema/context.json" 35 | }, 36 | "userContext": { 37 | "type": "object", 38 | "description": "User Context extension to carry with events", 39 | "additionalProperties": true 40 | } 41 | }, 42 | "required": [ 43 | "event", 44 | "sender", 45 | "aggregateId", 46 | "context", 47 | "opDate", 48 | "userContext" 49 | ] 50 | } -------------------------------------------------------------------------------- /spec/schema/published-event-wrapper.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://github.com/kloiasoft/eventapis/blob/master/spec/schema/published-event-wrapper.json", 4 | "title": "EventApis Published Event Wrapper", 5 | "description": "EventApis Published Event Wrapper Schema", 6 | "type": "object", 7 | "properties": { 8 | "event": { 9 | "type": "string", 10 | "description": "Encapsulated Published Event see event.json" 11 | }, 12 | "context": { 13 | "$ref": "https://github.com/kloiasoft/eventapis/blob/master/spec/schema/context.json" 14 | }, 15 | "sender": { 16 | "type": "string", 17 | "description": "Sender of Event - Service Name" 18 | }, 19 | "opDate": { 20 | "type": "integer", 21 | "description": "Event Send Epoch" 22 | }, 23 | "userContext": { 24 | "type": "object", 25 | "description": "User Context extension to carry with events", 26 | "additionalProperties": true 27 | } 28 | }, 29 | "required": [] 30 | } -------------------------------------------------------------------------------- /spec/schema/published-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://github.com/kloiasoft/eventapis/blob/master/spec/schema/published-event.json", 4 | "title": "EventApis Published Event", 5 | "description": "EventApis Published Event Content Schema", 6 | "type": "object", 7 | "properties": { 8 | "sender": { 9 | "$ref": "https://github.com/kloiasoft/eventapis/blob/master/spec/schema/published-event.json" 10 | }, 11 | "eventType": { 12 | "type": "string", 13 | "description": "State Change of the event for Operation", 14 | "enum": [ 15 | "OP_SINGLE", 16 | "OP_START", 17 | "EVENT", 18 | "OP_SUCCESS", 19 | "OP_FAIL" 20 | ] 21 | }, 22 | "eventName": { 23 | "type": "string", 24 | "description": "Name of the Event" 25 | } 26 | }, 27 | "additionalProperties": true, 28 | "required": [ 29 | "sender", 30 | "eventType", 31 | "eventName" 32 | ] 33 | } -------------------------------------------------------------------------------- /spec/spec-1.0.md: -------------------------------------------------------------------------------- 1 | service: Microservices in general, 2 types in general. BPM: Service 2 | command-service: Handler for Command/Events. (BPM Process) 3 | query-service: Query and Snapshot Handler. (BPM Process, Listener) 4 | 5 | event: Events Sourcing. fired by command-service, can be listened my zero or more command-service 6 | OP_EVENT: Ordinary Event 7 | TX_FAIL: Failure events 8 | TX_SUCCESS: Successful events 9 | OP_SINGLE: OP_EVENT+TX_SUCCESS together 10 | 11 | Snapshot: 12 | status: NOT_STARTED/IN_PROGRESS/DONE/ERROR 13 | 14 | ``` 15 | Operation 16 | id: operation-id (transaction-id -> uuid) 17 | handler-name: (command-service) (BPM Process) 18 | service-name: 19 | events: 20 | Event1: 21 | listeners: 22 | EventListener1: (command-service) (BPM Process) 23 | EventListener2: (command-service) (BPM Process) 24 | ... 25 | Event2: 26 | listeners: 27 | EventListener3: (command-service) (BPM Process) 28 | Event3: 29 | ... 30 | Event4: 31 | ... 32 | ... 33 | 34 | ``` -------------------------------------------------------------------------------- /spring-integration/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.splitet.core 9 | splitet-parent 10 | 0.8.0-SNAPSHOT 11 | 12 | 13 | spring-integration 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-dependencies 24 | ${spring.version} 25 | pom 26 | import 27 | 28 | 29 | org.springframework.cloud 30 | spring-cloud-dependencies 31 | ${spring.cloud.version} 32 | pom 33 | import 34 | 35 | 36 | 37 | 38 | 39 | 40 | io.splitet.core 41 | java-api 42 | ${project.version} 43 | 44 | 45 | org.springframework.kafka 46 | spring-kafka 47 | ${spring-kafka.version} 48 | 49 | 50 | org.apache.kafka 51 | kafka-clients 52 | ${kafka-clients.version} 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-starter 57 | true 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-starter-web 62 | true 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-starter-aop 67 | true 68 | 69 | 70 | org.springframework.cloud 71 | spring-cloud-starter-openfeign 72 | true 73 | 74 | 75 | org.projectlombok 76 | lombok 77 | 78 | 79 | javax.servlet 80 | javax.servlet-api 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /spring-integration/src/main/java/io/splitet/core/spring/configuration/AggregateListenerService.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.spring.configuration; 2 | 3 | import io.splitet.core.exception.EventStoreException; 4 | import io.splitet.core.pojos.Operation; 5 | import io.splitet.core.view.AggregateListener; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.kafka.clients.consumer.ConsumerRecord; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | import java.util.List; 13 | 14 | @Service 15 | @Slf4j 16 | public class AggregateListenerService { 17 | @Autowired(required = false) 18 | List aggregateListeners; 19 | 20 | 21 | @Transactional(rollbackFor = Exception.class) 22 | public void listenOperations(ConsumerRecord record) throws EventStoreException { 23 | for (AggregateListener snapshotRecorder : aggregateListeners) { 24 | snapshotRecorder.listenOperations(record); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spring-integration/src/main/java/io/splitet/core/spring/configuration/AutomaticTopicConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.spring.configuration; 2 | 3 | import io.splitet.core.common.PublishedEvent; 4 | import io.splitet.core.pojos.Operation; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.kafka.clients.admin.AdminClient; 7 | import org.apache.kafka.clients.admin.NewTopic; 8 | import org.apache.kafka.common.errors.UnknownTopicOrPartitionException; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.beans.factory.config.BeanDefinition; 11 | import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.core.type.filter.AssignableTypeFilter; 14 | import org.springframework.util.StopWatch; 15 | 16 | import javax.annotation.PostConstruct; 17 | import java.util.Collections; 18 | import java.util.Properties; 19 | import java.util.Set; 20 | import java.util.concurrent.ExecutionException; 21 | 22 | @Slf4j 23 | @Configuration 24 | public class AutomaticTopicConfiguration { 25 | public static final int DEFAULT_NUM_PARTITIONS = 3; 26 | public static final short DEFAULT_REPLICATION_FACTOR = 1; 27 | @Autowired 28 | private EventApisConfiguration eventApisConfiguration; 29 | 30 | private AdminClient adminClient() { 31 | Properties properties = new Properties(); 32 | properties.putAll(eventApisConfiguration.getEventBus().buildCommonProperties()); 33 | return AdminClient.create(properties); 34 | } 35 | 36 | @PostConstruct 37 | public void init() { 38 | AdminClient adminClient = adminClient(); 39 | try { 40 | StopWatch stopWatch = new StopWatch("CheckAndCreateTopics"); 41 | stopWatch.start("CheckAndCreateTopics"); 42 | ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); 43 | provider.addIncludeFilter(new AssignableTypeFilter(PublishedEvent.class)); 44 | Set candidateComponents = provider.findCandidateComponents(eventApisConfiguration.getBaseEventsPackage()); 45 | int numPartitions = getDefaultNumberOfPartitions(adminClient); 46 | for (BeanDefinition candidateComponent : candidateComponents) { 47 | Class beanClass; 48 | try { 49 | beanClass = (Class) Class.forName(candidateComponent.getBeanClassName()); 50 | String topicName = beanClass.getSimpleName(); 51 | log.info("Candidate {} to Create Topic:", topicName); 52 | try { 53 | adminClient.describeTopics(Collections.singleton(topicName)).all().get(); 54 | } catch (UnknownTopicOrPartitionException | ExecutionException exception) { 55 | if (!(exception.getCause() instanceof UnknownTopicOrPartitionException)) 56 | throw exception; 57 | log.warn("Topic {} does not exists, trying to create", topicName); 58 | try { 59 | adminClient.createTopics(Collections.singleton(new NewTopic(topicName, numPartitions, DEFAULT_REPLICATION_FACTOR))); 60 | log.info("Topic {} is Created Successfully:", topicName); 61 | } catch (Exception topicCreationEx) { 62 | log.warn("Error while creating Topic:" + topicCreationEx.getMessage(), topicCreationEx); 63 | } 64 | } 65 | } catch (ClassNotFoundException | InterruptedException | ExecutionException exception) { 66 | log.warn("Error while checking Topic:" + candidateComponent.toString() + " message: " + exception.getMessage(), exception); 67 | } 68 | } 69 | stopWatch.stop(); 70 | log.debug(stopWatch.prettyPrint()); 71 | } finally { 72 | adminClient.close(); 73 | } 74 | } 75 | 76 | private int getDefaultNumberOfPartitions(AdminClient adminClient) { 77 | try { 78 | return adminClient.describeTopics(Collections.singleton(Operation.OPERATION_EVENTS)) 79 | .all().get().values().iterator().next() 80 | .partitions().size(); 81 | } catch (InterruptedException | ExecutionException | NullPointerException e) { 82 | log.warn("Error while Calculating Number of Partitions from Topic: " + Operation.OPERATION_EVENTS + " Assuming " + DEFAULT_NUM_PARTITIONS); 83 | return DEFAULT_NUM_PARTITIONS; 84 | } 85 | } 86 | } 87 | 88 | 89 | -------------------------------------------------------------------------------- /spring-integration/src/main/java/io/splitet/core/spring/configuration/DataMigrationService.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.spring.configuration; 2 | 3 | 4 | import io.splitet.core.api.ViewQuery; 5 | import io.splitet.core.common.EventKey; 6 | import io.splitet.core.common.EventRecorder; 7 | import io.splitet.core.common.RecordedEvent; 8 | import io.splitet.core.exception.EventStoreException; 9 | import io.splitet.core.pojos.EventState; 10 | import io.splitet.core.view.Entity; 11 | import io.splitet.core.view.SnapshotRepository; 12 | 13 | import javax.annotation.Nullable; 14 | 15 | public class DataMigrationService { 16 | 17 | private EventRecorder eventRecorder; 18 | private ViewQuery viewQuery; 19 | private SnapshotRepository snapshotRepository; 20 | 21 | public DataMigrationService(EventRecorder eventRecorder, ViewQuery viewQuery, SnapshotRepository snapshotRepository) { 22 | this.eventRecorder = eventRecorder; 23 | this.viewQuery = viewQuery; 24 | this.snapshotRepository = snapshotRepository; 25 | } 26 | 27 | public T updateEvent(EventKey eventKey, boolean snapshot, @Nullable RecordedEvent newEventData, @Nullable EventState newEventState, @Nullable String newEventType) throws EventStoreException { 28 | eventRecorder.updateEvent(eventKey, newEventData, newEventState, newEventType); 29 | T entity = viewQuery.queryEntity(eventKey.getEntityId()); 30 | if (snapshot) 31 | entity = snapshotRepository.save(entity); 32 | return entity; 33 | } 34 | 35 | public T updateEvent(EventKey eventKey, boolean snapshot, RecordedEvent newEventData) throws EventStoreException { 36 | return this.updateEvent(eventKey, snapshot, newEventData, null, null); 37 | } 38 | 39 | public T snapshotOnly(String entityId) throws EventStoreException { 40 | T entity = viewQuery.queryEntity(entityId); 41 | return snapshotRepository.save(entity); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /spring-integration/src/main/java/io/splitet/core/spring/configuration/EventApisConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.spring.configuration; 2 | 3 | import io.splitet.core.cassandra.EventStoreConfig; 4 | import io.splitet.core.kafka.KafkaProperties; 5 | import lombok.Data; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.Map; 11 | 12 | @ConfigurationProperties("eventapis") 13 | @Component 14 | @Slf4j 15 | @Data 16 | public class EventApisConfiguration { 17 | private EventStoreConfig storeConfig; 18 | private KafkaProperties eventBus; 19 | private Map eventRecords; 20 | private String baseEventsPackage; 21 | 22 | public String getTableNameForEvents(String eventName) { 23 | return getEventRecords().getOrDefault(eventName, eventName); 24 | } 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /spring-integration/src/main/java/io/splitet/core/spring/configuration/EventMessageConverter.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.spring.configuration; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.databind.type.TypeFactory; 5 | import io.splitet.core.api.IUserContext; 6 | import io.splitet.core.common.Context; 7 | import io.splitet.core.common.OperationContext; 8 | import io.splitet.core.kafka.IOperationRepository; 9 | import io.splitet.core.kafka.PublishedEventWrapper; 10 | import io.splitet.core.pojos.EventState; 11 | import org.apache.kafka.clients.consumer.ConsumerRecord; 12 | import org.apache.kafka.common.errors.SerializationException; 13 | import org.springframework.kafka.support.converter.MessagingMessageConverter; 14 | 15 | import java.io.IOException; 16 | import java.lang.reflect.Type; 17 | 18 | /** 19 | * Created by zeldalozdemir on 25/04/2017. 20 | */ 21 | public class EventMessageConverter extends MessagingMessageConverter { 22 | private final ObjectMapper objectMapper; 23 | private OperationContext operationContext; 24 | private IUserContext userContext; 25 | private IOperationRepository operationRepository; 26 | 27 | 28 | public EventMessageConverter(ObjectMapper objectMapper, OperationContext operationContext, IUserContext userContext, IOperationRepository operationRepository) { 29 | this.objectMapper = objectMapper; 30 | this.operationContext = operationContext; 31 | this.userContext = userContext; 32 | this.operationRepository = operationRepository; 33 | } 34 | 35 | @Override 36 | public Object extractAndConvertValue(ConsumerRecord record, Type type) { 37 | Object value = record.value(); 38 | if (value instanceof PublishedEventWrapper) { 39 | PublishedEventWrapper eventWrapper = (PublishedEventWrapper) value; 40 | Context context = eventWrapper.getContext(); 41 | context.setCommandContext(record.topic()); 42 | operationContext.switchContext(context); 43 | userContext.extractUserContext(eventWrapper.getUserContext()); 44 | try { 45 | return objectMapper.readValue(eventWrapper.getEvent(), TypeFactory.rawClass(type)); 46 | } catch (IOException e) { 47 | operationRepository.failOperation(operationContext.getCommandContext(), event -> event.setEventState(EventState.TXN_FAILED)); 48 | throw new SerializationException(e); 49 | } 50 | } else 51 | return super.extractAndConvertValue(record, type); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /spring-integration/src/main/java/io/splitet/core/spring/configuration/FeignHelper.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.spring.configuration; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.splitet.core.common.OperationContext; 5 | import feign.Feign; 6 | import feign.RequestInterceptor; 7 | import feign.codec.ErrorDecoder; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.commons.io.IOUtils; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | import java.util.List; 15 | 16 | @Slf4j 17 | @Configuration 18 | public class FeignHelper { 19 | 20 | @Autowired 21 | OperationContext operationContext; 22 | @Autowired 23 | private ObjectMapper objectMapper; 24 | 25 | @Bean 26 | public ErrorDecoder errorDecoder() { 27 | return (methodKey, response) -> { 28 | String errorDesc = null; 29 | try { 30 | errorDesc = IOUtils.toString(response.body().asInputStream(), "utf-8"); 31 | return objectMapper.readValue(errorDesc, Exception.class); 32 | } catch (Exception e) { 33 | log.error("Feign error decoder exception : ", e); 34 | if (errorDesc != null) { 35 | return new Exception("Unclassified Error " + errorDesc); 36 | } else { 37 | return new Exception("Unclassified Error Unexpected Error"); 38 | } 39 | } 40 | }; 41 | } 42 | /* 43 | @Bean 44 | @Scope("prototype") 45 | public RequestInterceptor opIdInterceptor() { 46 | return template -> { 47 | String key = this.operationContext.getContextOpId(); 48 | if (key != null) { 49 | template.header(OpContextFilter.OP_ID_HEADER, key); 50 | // template.header(OperationContext.OP_ID, key); // legacy 51 | } 52 | 53 | }; 54 | }*/ 55 | 56 | @Bean 57 | public Feign.Builder feignBuilder(@Autowired List interceptors, @Autowired ErrorDecoder errorDecoder) { 58 | return Feign.builder() 59 | .requestInterceptors(interceptors).errorDecoder(errorDecoder); 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /spring-integration/src/main/java/io/splitet/core/spring/configuration/SpringKafkaOpListener.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.spring.configuration; 2 | 3 | import io.splitet.core.api.IUserContext; 4 | import io.splitet.core.exception.EventStoreException; 5 | import io.splitet.core.pojos.Operation; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.kafka.clients.consumer.ConsumerRecord; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.kafka.annotation.KafkaListener; 10 | import org.springframework.stereotype.Controller; 11 | 12 | @Controller 13 | @Slf4j 14 | public class SpringKafkaOpListener { 15 | @Autowired(required = false) 16 | AggregateListenerService aggregateListenerService; 17 | 18 | @Autowired 19 | IUserContext userContext; 20 | 21 | @KafkaListener(topics = Operation.OPERATION_EVENTS, containerFactory = "operationsKafkaListenerContainerFactory") 22 | void listenOperations(ConsumerRecord record) throws EventStoreException { 23 | String key = record.key(); 24 | Operation value = record.value(); 25 | log.debug("Trying Snapshot: " + key + " " + value); 26 | userContext.extractUserContext(value.getUserContext()); 27 | aggregateListenerService.listenOperations(record); 28 | } 29 | 30 | public void recover(Exception exception) throws Exception { 31 | log.error("Operation Handle is failed"); 32 | throw exception; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /spring-integration/src/main/java/io/splitet/core/spring/filter/OpContextFilter.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.spring.filter; 2 | 3 | import io.splitet.core.common.OperationContext; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.util.StringUtils; 7 | import org.springframework.web.filter.OncePerRequestFilter; 8 | 9 | import javax.servlet.FilterChain; 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | import java.util.Arrays; 15 | import java.util.Collections; 16 | import java.util.List; 17 | 18 | @Slf4j 19 | public class OpContextFilter extends OncePerRequestFilter { 20 | 21 | 22 | public static final List CHANGE_METHODS = Collections.unmodifiableList(Arrays.asList("POST", "PUT", "DELETE")); 23 | public static final String OP_ID_HEADER = "X-OPID"; 24 | public static final String OP_TIMEOUT_HEADER = "X-OP-TIMEOUT"; 25 | public static final String OP_START_TIME_HEADER = "X-OP-START-TIME"; 26 | public static final String PARENT_OP_ID_HEADER = "X-PARENT-OPID"; 27 | private OperationContext operationContext; 28 | 29 | @Autowired 30 | public OpContextFilter(OperationContext operationContext) { 31 | this.operationContext = operationContext; 32 | } 33 | 34 | @Override 35 | protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { 36 | try { 37 | if (CHANGE_METHODS.contains(httpServletRequest.getMethod())) { 38 | 39 | String parentOpId = httpServletRequest.getHeader(OP_ID_HEADER); 40 | String olderOpIds = httpServletRequest.getHeader(PARENT_OP_ID_HEADER); 41 | if (StringUtils.hasText(parentOpId) && StringUtils.hasText(olderOpIds)) { 42 | parentOpId = parentOpId + OperationContext.PARENT_OP_ID_DELIMITER + olderOpIds; 43 | } 44 | String opId = operationContext.generateContext(StringUtils.hasText(parentOpId) ? parentOpId : null, true); 45 | httpServletResponse.setHeader(OperationContext.OP_ID, opId); //legacy 46 | httpServletResponse.setHeader(OP_ID_HEADER, opId); 47 | // httpServletResponse.setHeader(PARENT_OP_ID_HEADER, parentOpId); 48 | operationContext.getContext().getPreGenerationConsumers().add(generatedContext -> { 49 | httpServletResponse.setHeader(OP_TIMEOUT_HEADER, String.valueOf(generatedContext.getCommandTimeout())); 50 | httpServletResponse.setHeader(OP_START_TIME_HEADER, String.valueOf(generatedContext.getStartTime())); 51 | }); 52 | } 53 | } finally { 54 | filterChain.doFilter(httpServletRequest, httpServletResponse); 55 | operationContext.clearContext(); 56 | } 57 | } 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /spring-jpa-view/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.splitet.core 9 | splitet-parent 10 | 0.8.0-SNAPSHOT 11 | 12 | 13 | spring-jpa-view 14 | 15 | 1.1.3 16 | 17 | 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-dependencies 24 | ${spring.version} 25 | pom 26 | import 27 | 28 | 29 | 30 | 31 | 32 | io.splitet.core 33 | spring-integration 34 | ${project.version} 35 | 36 | 37 | org.projectlombok 38 | lombok 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-data-jpa 43 | ${spring.version} 44 | 45 | 46 | org.springframework.kafka 47 | spring-kafka 48 | ${spring-kafka.version} 49 | 50 | 51 | com.querydsl 52 | querydsl-apt 53 | 54 | 55 | 56 | 57 | 58 | com.mysema.maven 59 | apt-maven-plugin 60 | ${apt.maven.version} 61 | 62 | 63 | 64 | process 65 | 66 | 67 | target/generated-sources/java 68 | com.querydsl.apt.jpa.JPAAnnotationProcessor 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /spring-jpa-view/src/main/java/io/splitet/core/spring/model/JpaEntity.java: -------------------------------------------------------------------------------- 1 | package io.splitet.core.spring.model; 2 | 3 | import io.splitet.core.common.EventKey; 4 | import io.splitet.core.view.Entity; 5 | import lombok.Data; 6 | 7 | import javax.persistence.Id; 8 | import javax.persistence.MappedSuperclass; 9 | 10 | @Data 11 | @MappedSuperclass 12 | public abstract class JpaEntity implements Entity { 13 | 14 | @Id 15 | private String id; 16 | private int version; 17 | 18 | public JpaEntity() { 19 | } 20 | 21 | public JpaEntity(String id, int version) { 22 | this.id = id; 23 | this.version = version; 24 | } 25 | 26 | @Override 27 | public EventKey getEventKey() { 28 | return new EventKey(id, version); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /travis/settings.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | jfrog-releases 6 | ${env.MVN_JFROG_USERNAME} 7 | ${env.MVN_JFROG_PASSWORD} 8 | 9 | 10 | jfrog-snapshots 11 | ${env.MVN_JFROG_SNAPSHOT_USERNAME} 12 | ${env.MVN_JFROG_SNAPSHOT_PASSWORD} 13 | 14 | 15 | maven.oracle.com 16 | ${env.MVN_ORACLE_USERNAME} 17 | ${env.MVN_ORACLE_PASSWORD} 18 | 19 | 20 | ANY 21 | ANY 22 | OAM 11g 23 | 24 | 25 | 26 | 27 | 28 | http.protocol.allow-circular-redirects 29 | %b,true 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | --------------------------------------------------------------------------------