├── README.md ├── docker-compose.yml ├── pom.xml ├── src └── main │ ├── java │ └── com │ │ └── lostsys │ │ └── sample │ │ └── hexagonal │ │ ├── Application.java │ │ ├── application │ │ ├── CustomerUseCase.java │ │ ├── MessageBrokerUseCase.java │ │ └── OrderUserCase.java │ │ ├── domain │ │ ├── Customer.java │ │ └── Orders.java │ │ └── infra │ │ ├── inputadapter │ │ ├── http │ │ │ ├── CustomerAPI.java │ │ │ └── OrderAPI.java │ │ └── message │ │ │ └── KafkaMessage.java │ │ ├── inputport │ │ ├── CustomerInputPort.java │ │ ├── MessageBrokerInputPort.java │ │ └── OrderInputPort.java │ │ ├── outputadapter │ │ ├── postgresrepository │ │ │ └── PostgresRepository.java │ │ └── redisrepository │ │ │ └── RedisRepository.java │ │ ├── outputport │ │ ├── CommandRepository.java │ │ └── QueryRepository.java │ │ └── utils │ │ └── ConversionUtils.java │ └── resources │ ├── application.properties │ └── schema.sql ├── testReadCQRS.sh ├── testReadNormal.sh └── testWriter.sh /README.md: -------------------------------------------------------------------------------- 1 | # java-springboot-cqrs-architecture-sample 2 | CQRS architecture sample using Java and Springboot 3 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | services: 4 | zookeeper: 5 | container_name: zookeeper 6 | image: docker.io/bitnami/zookeeper:3.7 7 | ports: 8 | - "2181:2181" 9 | environment: 10 | - ALLOW_ANONYMOUS_LOGIN=yes 11 | kafka: 12 | container_name: kafka 13 | image: docker.io/bitnami/kafka:2 14 | ports: 15 | - "9092:9092" 16 | - "29092:29092" 17 | environment: 18 | - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,EXTERNALPLAINTEXT://:29092 19 | - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,EXTERNALPLAINTEXT://localhost:29092 20 | - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,EXTERNALPLAINTEXT:PLAINTEXT 21 | - KAFKA_INTER_BROKER_LISTENER_NAME=EXTERNALPLAINTEXT 22 | - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 23 | - ALLOW_PLAINTEXT_LISTENER=yes 24 | depends_on: 25 | - zookeeper 26 | postgres: 27 | container_name: postgres 28 | image: postgres:11 29 | environment: 30 | POSTGRES_USER: postgres 31 | POSTGRES_PASSWORD: postgres 32 | POSTGRES_DB: postgresdb 33 | PGDATA: /data/postgres 34 | ports: 35 | - "5432:5432" 36 | command: [ "postgres", "-c", "wal_level=logical", "-c", "max_wal_senders=1" , "-c", "max_replication_slots=1" ] 37 | restart: unless-stopped 38 | 39 | debezium: 40 | container_name: debezium 41 | image: debezium/connect 42 | environment: 43 | GROUP_ID: 1 44 | CONFIG_STORAGE_TOPIC: my-connect-configs 45 | OFFSET_STORAGE_TOPIC: my-connect-offsets 46 | BOOTSTRAP_SERVERS: kafka:9092 47 | ADVERTISED_HOST_NAME: debezium 48 | ports: 49 | - "8083:8083" 50 | depends_on: 51 | - kafka 52 | 53 | redis: 54 | container_name: redis 55 | image: redis:7.0.5-alpine 56 | ports: 57 | - "6379:6379" 58 | command: [ "redis-server", "--requirepass", "SUPER_SECRET_PASSWORD" ] 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.lostsys 6 | sample-cqrs-architecture 7 | 1.0.0-SNAPSHOT 8 | jar 9 | 10 | Sample CQRS Architecture 11 | 12 | 13 | 14 | 11 15 | UTF-8 16 | UTF-8 17 | 2.7.3 18 | 1.18.24 19 | 42.4.2 20 | 2.13.4 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-dependencies 28 | ${springboot.version} 29 | pom 30 | import 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-web 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-jdbc 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-data-redis 47 | 48 | 49 | org.springframework.kafka 50 | spring-kafka 51 | 52 | 53 | org.projectlombok 54 | lombok 55 | ${lombok.version} 56 | true 57 | 58 | 59 | org.postgresql 60 | postgresql 61 | runtime 62 | 63 | 64 | com.fasterxml.jackson.core 65 | jackson-databind 66 | ${jackson.version} 67 | 68 | 69 | redis.clients 70 | jedis 71 | 72 | 73 | 74 | 75 | 76 | 77 | true 78 | src/main/resources 79 | 80 | 81 | 82 | 83 | org.apache.maven.plugins 84 | maven-resources-plugin 85 | 3.2.0 86 | 87 | 88 | org.springframework.boot 89 | spring-boot-maven-plugin 90 | ${springboot.version} 91 | 92 | 93 | 94 | org.projectlombok 95 | lombok 96 | 97 | 98 | 99 | 100 | 101 | 102 | repackage 103 | 104 | 105 | 106 | 107 | 108 | org.apache.maven.plugins 109 | maven-compiler-plugin 110 | 3.8.1 111 | 112 | ${java.version} 113 | ${java.version} 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/Application.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.data.redis.connection.RedisConnectionFactory; 8 | import org.springframework.data.redis.core.RedisTemplate; 9 | 10 | @SpringBootApplication 11 | @Configuration 12 | public class Application { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(Application.class, args); 16 | } 17 | 18 | @Bean 19 | public RedisTemplate redisTemplate( RedisConnectionFactory connectionFactory ) { 20 | RedisTemplate template = new RedisTemplate<>(); 21 | template.setConnectionFactory( connectionFactory ); 22 | return template; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/application/CustomerUseCase.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal.application; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | 9 | import com.lostsys.sample.hexagonal.domain.Customer; 10 | import com.lostsys.sample.hexagonal.infra.inputport.CustomerInputPort; 11 | import com.lostsys.sample.hexagonal.infra.outputport.CommandRepository; 12 | 13 | @Component 14 | public class CustomerUseCase implements CustomerInputPort { 15 | 16 | @Autowired 17 | CommandRepository entityRepository; 18 | 19 | @Override 20 | public Customer createCustomer(String name, String country) { 21 | Customer customer = Customer.builder() 22 | .id( UUID.randomUUID().toString() ) 23 | .name( name ) 24 | .country( country ) 25 | .build(); 26 | 27 | return entityRepository.save( customer ); 28 | } 29 | 30 | @Override 31 | public Customer getById(String customerId) { 32 | return entityRepository.getById( customerId, Customer.class ); 33 | } 34 | 35 | @Override 36 | public List getAll() { 37 | return entityRepository.getAll( Customer.class ); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/application/MessageBrokerUseCase.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal.application; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | 9 | import com.lostsys.sample.hexagonal.domain.Customer; 10 | import com.lostsys.sample.hexagonal.domain.Orders; 11 | import com.lostsys.sample.hexagonal.infra.inputport.MessageBrokerInputPort; 12 | import com.lostsys.sample.hexagonal.infra.outputport.QueryRepository; 13 | 14 | @Component 15 | public class MessageBrokerUseCase implements MessageBrokerInputPort { 16 | 17 | @Autowired 18 | QueryRepository queryRepository; 19 | 20 | Map> classes = Map.of( 21 | "customer", Customer.class, 22 | "orders", Orders.class 23 | ); 24 | 25 | @Override 26 | public void deleteReg(String table, Map reg) { 27 | queryRepository.delete( (String) reg.get("id"), classes.get( table ) ); 28 | } 29 | 30 | @Override 31 | public void updateReg(String table, Map reg) { 32 | queryRepository.save( reg, classes.get( table ) ); 33 | } 34 | 35 | @Override 36 | public void insertReg(String table, Map reg) { 37 | queryRepository.save( reg, classes.get( table ) ); 38 | } 39 | 40 | @Override 41 | public List> getAll(String table) { 42 | return queryRepository.getAll( classes.get( table ) ); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/application/OrderUserCase.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal.application; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.UUID; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | 9 | import com.lostsys.sample.hexagonal.domain.Orders; 10 | import com.lostsys.sample.hexagonal.infra.inputport.OrderInputPort; 11 | import com.lostsys.sample.hexagonal.infra.outputport.CommandRepository; 12 | 13 | @Component 14 | public class OrderUserCase implements OrderInputPort { 15 | 16 | @Autowired 17 | CommandRepository entityRepository; 18 | 19 | @Override 20 | public Orders createOrder(String customerId, BigDecimal total) { 21 | Orders order = Orders.builder() 22 | .id( UUID.randomUUID().toString() ) 23 | .customerId( customerId ) 24 | .total( total ) 25 | .build(); 26 | 27 | return entityRepository.save( order ); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/domain/Customer.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal.domain; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | @Builder 8 | @Getter 9 | @Setter 10 | public class Customer { 11 | private String id; 12 | private String name; 13 | private String country; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/domain/Orders.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal.domain; 2 | 3 | import java.math.BigDecimal; 4 | 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | @Builder 10 | @Getter 11 | @Setter 12 | public class Orders { 13 | private String id; 14 | private String customerId; 15 | private BigDecimal total; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/infra/inputadapter/http/CustomerAPI.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal.infra.inputadapter.http; 2 | 3 | 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.jdbc.core.JdbcTemplate; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RequestParam; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | import com.lostsys.sample.hexagonal.domain.Customer; 16 | import com.lostsys.sample.hexagonal.infra.inputport.CustomerInputPort; 17 | import com.lostsys.sample.hexagonal.infra.inputport.MessageBrokerInputPort; 18 | 19 | @RestController 20 | @RequestMapping(value = "customer") 21 | public class CustomerAPI { 22 | 23 | @Autowired 24 | CustomerInputPort customerInputPort; 25 | 26 | @Autowired 27 | MessageBrokerInputPort messageBrokerInputPort; 28 | 29 | @Autowired 30 | JdbcTemplate jdbcTemplate; 31 | 32 | @PostMapping(value = "create", produces=MediaType.APPLICATION_JSON_VALUE) 33 | public Customer create( @RequestParam String name, @RequestParam String country ) { 34 | return customerInputPort.createCustomer(name, country); 35 | } 36 | 37 | @PostMapping(value = "get", produces=MediaType.APPLICATION_JSON_VALUE) 38 | public Customer get( @RequestParam String customerId ) { 39 | return customerInputPort.getById(customerId); 40 | } 41 | 42 | @PostMapping(value = "getall", produces=MediaType.APPLICATION_JSON_VALUE) 43 | public List getAll() { 44 | return customerInputPort.getAll(); 45 | } 46 | 47 | @PostMapping(value = "getallCustomerCQRS", produces=MediaType.APPLICATION_JSON_VALUE) 48 | public List> getallCQRS() { 49 | return messageBrokerInputPort.getAll( "customer" ); 50 | } 51 | 52 | @PostMapping(value = "getallCustomerNormal", produces=MediaType.APPLICATION_JSON_VALUE) 53 | public List getallCustomerNormal() { 54 | return (List) jdbcTemplate.queryForList("SELECT *, (SELECT array_to_json(array_agg(row_to_json(t))) FROM (SELECT row_to_json(o) FROM Orders o WHERE o.customerId=c.id) t) AS orders FROM Customer c;"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/infra/inputadapter/http/OrderAPI.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal.infra.inputadapter.http; 2 | 3 | import java.math.BigDecimal; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RequestParam; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import com.lostsys.sample.hexagonal.domain.Orders; 13 | import com.lostsys.sample.hexagonal.infra.inputport.OrderInputPort; 14 | 15 | @RestController 16 | @RequestMapping(value = "order") 17 | public class OrderAPI { 18 | 19 | @Autowired 20 | OrderInputPort orderInputPort; 21 | 22 | @PostMapping(value = "create", produces=MediaType.APPLICATION_JSON_VALUE) 23 | public Orders create( @RequestParam String customerId, @RequestParam BigDecimal total ) { 24 | return orderInputPort.createOrder(customerId, total); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/infra/inputadapter/message/KafkaMessage.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal.infra.inputadapter.message; 2 | 3 | import java.util.Map; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.kafka.annotation.KafkaListener; 7 | import org.springframework.messaging.handler.annotation.Payload; 8 | import org.springframework.stereotype.Service; 9 | 10 | import com.lostsys.sample.hexagonal.infra.inputport.MessageBrokerInputPort; 11 | import com.lostsys.sample.hexagonal.infra.utils.ConversionUtils; 12 | 13 | @Service 14 | public class KafkaMessage { 15 | 16 | @Autowired 17 | MessageBrokerInputPort messageBrokerInputPort; 18 | 19 | @KafkaListener(topicPattern = "dbserver1.public.*", groupId = "group1") 20 | public void consumeEvent( @Payload( required = false ) String eventMsg ) { 21 | if ( eventMsg == null ) return; 22 | 23 | Map event = ConversionUtils.jsonstring2Map( eventMsg ); 24 | 25 | Map payload = (Map) event.get("payload"); 26 | String operation = (String) payload.get("op"); 27 | String table = (String) ((Map) payload.get("source")).get("table"); 28 | 29 | if ( operation.equals("u") ) { 30 | messageBrokerInputPort.updateReg(table, (Map) payload.get("after")); 31 | } else if ( operation.equals("c") ) { 32 | messageBrokerInputPort.insertReg(table, (Map) payload.get("after")); 33 | } else if ( operation.equals("d") ) { 34 | messageBrokerInputPort.deleteReg(table, (Map) payload.get("before")); 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/infra/inputport/CustomerInputPort.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal.infra.inputport; 2 | 3 | import java.util.List; 4 | 5 | import com.lostsys.sample.hexagonal.domain.Customer; 6 | 7 | public interface CustomerInputPort { 8 | 9 | public Customer createCustomer(String name, String country); 10 | 11 | public Customer getById(String customerId); 12 | 13 | public List getAll(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/infra/inputport/MessageBrokerInputPort.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal.infra.inputport; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public interface MessageBrokerInputPort { 7 | 8 | public void deleteReg( String table, Map reg ); 9 | public void updateReg( String table, Map reg ); 10 | public void insertReg( String table, Map reg ); 11 | public List> getAll( String table ); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/infra/inputport/OrderInputPort.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal.infra.inputport; 2 | 3 | import java.math.BigDecimal; 4 | 5 | import com.lostsys.sample.hexagonal.domain.Orders; 6 | 7 | public interface OrderInputPort { 8 | public Orders createOrder( String customerId, BigDecimal total ); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/infra/outputadapter/postgresrepository/PostgresRepository.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal.infra.outputadapter.postgresrepository; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.lang.reflect.Method; 6 | import java.sql.ResultSet; 7 | import java.sql.SQLException; 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.jdbc.core.JdbcTemplate; 13 | import org.springframework.jdbc.core.RowMapper; 14 | import org.springframework.stereotype.Component; 15 | 16 | import com.lostsys.sample.hexagonal.infra.outputport.CommandRepository; 17 | 18 | @Component 19 | public class PostgresRepository implements CommandRepository { 20 | 21 | @Autowired 22 | JdbcTemplate jdbcTemplate; 23 | 24 | @Override 25 | public T save(T reg) { 26 | 27 | Field[] entityFields = reg.getClass().getDeclaredFields(); 28 | 29 | String[] fields = new String[ entityFields.length ]; 30 | Object[] fieldValues = new Object[ entityFields.length ]; 31 | 32 | try { 33 | for ( int i=0; i T getById(String id, Class clazz) { 58 | List list = jdbcTemplate.query("SELECT * FROM "+clazz.getSimpleName()+" WHERE id = ?", 59 | new LombokRowMapper( clazz ), 60 | id ); 61 | 62 | if ( !list.isEmpty() ) return list.get(0); 63 | 64 | return null; 65 | } 66 | 67 | @Override 68 | public List getAll(Class clazz) { 69 | return jdbcTemplate.query("SELECT * FROM "+clazz.getSimpleName(), new LombokRowMapper( clazz ) ); 70 | } 71 | 72 | private class LombokRowMapper implements RowMapper { 73 | private Class clazz = null; 74 | 75 | public LombokRowMapper( Class clazz ) { 76 | this.clazz = clazz; 77 | } 78 | 79 | @Override 80 | public T mapRow(ResultSet rs, int rowNum) throws SQLException { 81 | 82 | try { 83 | Method builderMethod = clazz.getMethod("builder"); 84 | if ( builderMethod == null ) return null; 85 | 86 | Object row = builderMethod.invoke(null); 87 | Method[] m = row.getClass().getDeclaredMethods(); 88 | 89 | for ( int i=0; i redisTemplate; 22 | 23 | @Override 24 | public void save(Map reg, Class clazz) { 25 | redisTemplate.opsForHash().put( 26 | getHashFromClass( clazz ), 27 | reg.get("id"), 28 | ConversionUtils.map2Jsonstring( reg ) 29 | ); 30 | 31 | if ( clazz.equals( Orders.class ) ) addOrderToCustomer( reg, true ); 32 | } 33 | 34 | @Override 35 | public void delete(String id, Class clazz) { 36 | if ( clazz.equals( Orders.class ) ) addOrderToCustomer( id, false ); 37 | 38 | redisTemplate.opsForHash().delete( 39 | getHashFromClass( clazz ), 40 | id 41 | ); 42 | } 43 | 44 | @Override 45 | public Map getById(String id, Class clazz) { 46 | return ConversionUtils.jsonstring2Map( 47 | (String) redisTemplate.opsForHash().get( 48 | getHashFromClass( clazz ), 49 | id 50 | ) 51 | ); 52 | } 53 | 54 | @Override 55 | public List> getAll(Class clazz) { 56 | return redisTemplate.opsForHash() 57 | .values( getHashFromClass( clazz ) ) 58 | .stream() 59 | .map( el -> ConversionUtils.jsonstring2Map( (String) el ) ) 60 | .collect( Collectors.toList() ); 61 | } 62 | 63 | private void addOrderToCustomer( String orderId, boolean appendOrder ) { 64 | addOrderToCustomer( 65 | getById( orderId, Orders.class), 66 | appendOrder 67 | ); 68 | } 69 | 70 | private void addOrderToCustomer( Map order, boolean appendOrder ) { 71 | String customerId = (String) order.get("customerid"); 72 | if ( customerId == null ) return; 73 | 74 | Map customer = ConversionUtils.jsonstring2Map( 75 | (String) redisTemplate.opsForHash().get( 76 | getHashFromClass( Customer.class ), 77 | customerId 78 | ) 79 | ); 80 | 81 | List> orders = (List>) customer.get("orders"); 82 | if ( orders == null ) orders = new ArrayList<>(); 83 | 84 | orders = orders.stream() 85 | .filter( el -> !((Map) el).get("id").equals( order.get("id") ) ) 86 | .collect( Collectors.toList() ); 87 | 88 | if ( appendOrder ) orders.add( order ); 89 | 90 | customer.put("orders", orders); 91 | 92 | redisTemplate.opsForHash().put( 93 | getHashFromClass( Customer.class ), 94 | customerId, 95 | ConversionUtils.map2Jsonstring( customer ) 96 | ); 97 | } 98 | 99 | private String getHashFromClass( Class clazz ) { 100 | return clazz.getName().replace('.', '_'); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/infra/outputport/CommandRepository.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal.infra.outputport; 2 | 3 | import java.util.List; 4 | 5 | public interface CommandRepository { 6 | 7 | public T save( T reg ); 8 | 9 | public T getById( String id, Class clazz ); 10 | 11 | public List getAll( Class clazz ); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/infra/outputport/QueryRepository.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal.infra.outputport; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public interface QueryRepository { 7 | 8 | public void save( Map reg, Class clazz ); 9 | 10 | public void delete( String id, Class clazz ); 11 | 12 | public Map getById( String id, Class clazz ); 13 | 14 | public List> getAll( Class clazz ); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/lostsys/sample/hexagonal/infra/utils/ConversionUtils.java: -------------------------------------------------------------------------------- 1 | package com.lostsys.sample.hexagonal.infra.utils; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import com.fasterxml.jackson.core.JsonProcessingException; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | 9 | public class ConversionUtils { 10 | 11 | private static ObjectMapper objectMapper = new ObjectMapper(); 12 | 13 | private ConversionUtils() {} 14 | 15 | public static String map2Jsonstring( Map map ) { 16 | if ( map == null ) return "{}"; 17 | 18 | try { 19 | return objectMapper.writeValueAsString( map ); 20 | } catch (JsonProcessingException e) { 21 | e.printStackTrace(); 22 | } 23 | 24 | return "{}"; 25 | } 26 | 27 | public static Map jsonstring2Map( String json ) { 28 | if ( json == null ) return new HashMap(); 29 | 30 | try { 31 | return objectMapper.readValue( json, Map.class); 32 | } catch (JsonProcessingException e) { 33 | e.printStackTrace(); 34 | } 35 | 36 | return new HashMap(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | spring.datasource.url=jdbc:postgresql://localhost:5432/postgresdb 5 | spring.datasource.username=postgres 6 | spring.datasource.password=postgres 7 | 8 | spring.jpa.hibernate.ddl-auto=none 9 | spring.jpa.defer-datasource-initialization=true 10 | spring.sql.init.mode=always 11 | 12 | spring.datasource.schema=classpath:schema.sql 13 | 14 | spring.redis.host = 127.0.0.1 15 | spring.redis.port = 6379 16 | spring.redis.password = SUPER_SECRET_PASSWORD 17 | 18 | spring.kafka.consumer.bootstrap-servers=127.0.0.1:29092 19 | 20 | #logging.level.root=ERROR -------------------------------------------------------------------------------- /src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS Customer 2 | ( 3 | id TEXT NOT NULL PRIMARY KEY, 4 | name TEXT NOT NULL, 5 | country TEXT NOT NULL 6 | ); 7 | 8 | CREATE TABLE IF NOT EXISTS Orders 9 | ( 10 | id TEXT NOT NULL PRIMARY KEY, 11 | customerId TEXT NOT NULL, 12 | total NUMERIC(10,2) NOT NULL 13 | ); -------------------------------------------------------------------------------- /testReadCQRS.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curl -X POST http://127.0.0.1:8080/customer/getallCustomerCQRS 4 | 5 | 6 | -------------------------------------------------------------------------------- /testReadNormal.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curl -X POST http://127.0.0.1:8080/customer/getallCustomerNormal 4 | 5 | 6 | -------------------------------------------------------------------------------- /testWriter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | for (( i=1; i<=150; i++ )) 5 | do 6 | c=$(curl -s -X POST http://127.0.0.1:8080/customer/create -d "name=pepe&country=ES" | jq -r '.id') 7 | 8 | for (( n=1; n<=100; n++ )) 9 | do 10 | o=$(curl -s -X POST http://127.0.0.1:8080/order/create -d "customerId=$c&total=$n") 11 | done 12 | 13 | echo -n "." 14 | done 15 | 16 | --------------------------------------------------------------------------------