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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | ```
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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-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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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