Repository: amigoscode/microservices Branch: main Commit: 12dcd5d4758b Files: 76 Total size: 655.9 KB Directory structure: gitextract_8gj87pry/ ├── .gitignore ├── README.md ├── amqp/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── amigoscode/ │ └── amqp/ │ ├── RabbitMQConfig.java │ └── RabbitMQMessageProducer.java ├── apigw/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── amigoscode/ │ │ └── apigw/ │ │ └── ApiGWApplication.java │ └── resources/ │ ├── application-docker.yml │ ├── application.yml │ └── banner.txt ├── clients/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── amigoscode/ │ │ └── clients/ │ │ ├── fraud/ │ │ │ ├── FraudCheckResponse.java │ │ │ └── FraudClient.java │ │ └── notification/ │ │ ├── NotificationClient.java │ │ └── NotificationRequest.java │ └── resources/ │ ├── clients-default.properties │ ├── clients-docker.properties │ └── clients-kube.properties ├── customer/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── amigoscode/ │ │ └── customer/ │ │ ├── Customer.java │ │ ├── CustomerApplication.java │ │ ├── CustomerController.java │ │ ├── CustomerRegistrationRequest.java │ │ ├── CustomerRepository.java │ │ └── CustomerService.java │ └── resources/ │ ├── application-docker.yml │ ├── application-kube.yml │ ├── application.yml │ └── banner.txt ├── diagrams.drawio ├── docker-compose.yml ├── eureka-server/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── amigoscode/ │ │ └── eurekaserver/ │ │ └── EurekaServerApplication.java │ └── resources/ │ ├── application-docker.yml │ ├── application.yml │ └── banner.txt ├── fraud/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── amigoscode/ │ │ └── fraud/ │ │ ├── FraudApplication.java │ │ ├── FraudCheckHistory.java │ │ ├── FraudCheckHistoryRepository.java │ │ ├── FraudCheckService.java │ │ └── FraudController.java │ └── resources/ │ ├── application-docker.yml │ ├── application-kube.yml │ ├── application.yml │ └── banner.txt ├── k8s/ │ └── minikube/ │ ├── bootstrap/ │ │ ├── postgres/ │ │ │ ├── configmap.yml │ │ │ ├── service.yml │ │ │ ├── statefulset.yml │ │ │ └── volume.yml │ │ ├── rabbitmq/ │ │ │ ├── README.md │ │ │ ├── configmap.yaml │ │ │ ├── rbac.yaml │ │ │ ├── services.yaml │ │ │ └── statefulset.yaml │ │ └── zipkin/ │ │ ├── service.yml │ │ └── statefulset.yml │ └── services/ │ ├── customer/ │ │ ├── deployment.yml │ │ └── service.yml │ ├── fraud/ │ │ ├── deployment.yml │ │ └── service.yml │ └── notification/ │ ├── deployment.yml │ └── service.yml ├── notification/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── amigoscode/ │ │ └── notification/ │ │ ├── Notification.java │ │ ├── NotificationApplication.java │ │ ├── NotificationConfig.java │ │ ├── NotificationController.java │ │ ├── NotificationRepository.java │ │ ├── NotificationService.java │ │ └── rabbitmq/ │ │ └── NotificationConsumer.java │ └── resources/ │ ├── application-docker.yml │ ├── application-kube.yml │ ├── application.yml │ └── banner.txt └── pom.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ HELP.md target/ **/target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ .idea/ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans .sts4-cache ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr ### NetBeans ### /nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ build/ !**/src/main/**/build/ !**/src/test/**/build/ ### VS Code ### .vscode/ ================================================ FILE: README.md ================================================ # Microservices ![Screenshot 2021-11-30 at 12 32 51](https://user-images.githubusercontent.com/40702606/144061535-7a42e85b-59d6-4f7f-9c35-18a48b49e6de.png) ================================================ FILE: amqp/pom.xml ================================================ amigosservices com.amigoscode 1.0-SNAPSHOT 4.0.0 jar amqp org.springframework.boot spring-boot-starter-amqp ================================================ FILE: amqp/src/main/java/com/amigoscode/amqp/RabbitMQConfig.java ================================================ package com.amigoscode.amqp; import lombok.AllArgsConstructor; import org.springframework.amqp.core.AmqpTemplate; import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @AllArgsConstructor public class RabbitMQConfig { private final ConnectionFactory connectionFactory; @Bean public AmqpTemplate amqpTemplate() { RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); rabbitTemplate.setMessageConverter(jacksonConverter()); return rabbitTemplate; } @Bean public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory() { SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); factory.setMessageConverter(jacksonConverter()); return factory; } @Bean public MessageConverter jacksonConverter() { MessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter(); return jackson2JsonMessageConverter; } } ================================================ FILE: amqp/src/main/java/com/amigoscode/amqp/RabbitMQMessageProducer.java ================================================ package com.amigoscode.amqp; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.AmqpTemplate; import org.springframework.stereotype.Component; @Component @Slf4j @AllArgsConstructor public class RabbitMQMessageProducer { private final AmqpTemplate amqpTemplate; public void publish(Object payload, String exchange, String routingKey) { log.info("Publishing to {} using routingKey {}. Payload: {}", exchange, routingKey, payload); amqpTemplate.convertAndSend(exchange, routingKey, payload); log.info("Published to {} using routingKey {}. Payload: {}", exchange, routingKey, payload); } } ================================================ FILE: apigw/pom.xml ================================================ amigosservices com.amigoscode 1.0-SNAPSHOT 4.0.0 jar apigw org.springframework.boot spring-boot-maven-plugin build-docker-image com.google.cloud.tools jib-maven-plugin org.springframework.cloud spring-cloud-starter-gateway org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-sleuth org.springframework.cloud spring-cloud-sleuth-zipkin ================================================ FILE: apigw/src/main/java/com/amigoscode/apigw/ApiGWApplication.java ================================================ package com.amigoscode.apigw; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @EnableEurekaClient @SpringBootApplication public class ApiGWApplication { public static void main(String[] args) { SpringApplication.run(ApiGWApplication.class, args); } } ================================================ FILE: apigw/src/main/resources/application-docker.yml ================================================ server: port: 8083 spring: application: name: api-gateway zipkin: base-url: http://zipkin:9411 cloud: gateway: routes: - id: customer uri: lb://CUSTOMER predicates: - Path=/api/v1/customers/** eureka: client: service-url: defaultZone: http://eureka-server:8761/eureka fetch-registry: true register-with-eureka: true ================================================ FILE: apigw/src/main/resources/application.yml ================================================ server: port: 8083 spring: application: name: api-gateway zipkin: base-url: http://localhost:9411 cloud: gateway: routes: - id: customer uri: lb://CUSTOMER predicates: - Path=/api/v1/customers/** eureka: client: service-url: defaultZone: http://localhost:8761/eureka fetch-registry: true register-with-eureka: true ================================================ FILE: apigw/src/main/resources/banner.txt ================================================ ,---. ,------. ,--. ,----. ,--. ,--. / O \ | .--. ' | | ' .-./ | | | | | .-. | | '--' | | | | | .---. | |.'.| | | | | | | | --' | | ' '--' | | ,'. | `--' `--' `--' `--' `------' '--' '--' ${application.title} ${application.version} Powered by Spring Boot ${spring-boot.version} ================================================ FILE: clients/pom.xml ================================================ amigosservices com.amigoscode 1.0-SNAPSHOT 4.0.0 jar clients ================================================ FILE: clients/src/main/java/com/amigoscode/clients/fraud/FraudCheckResponse.java ================================================ package com.amigoscode.clients.fraud; public record FraudCheckResponse(Boolean isFraudster) { } ================================================ FILE: clients/src/main/java/com/amigoscode/clients/fraud/FraudClient.java ================================================ package com.amigoscode.clients.fraud; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient( name = "fraud", url = "${clients.fraud.url}" ) public interface FraudClient { @GetMapping(path = "api/v1/fraud-check/{customerId}") FraudCheckResponse isFraudster( @PathVariable("customerId") Integer customerId); } ================================================ FILE: clients/src/main/java/com/amigoscode/clients/notification/NotificationClient.java ================================================ package com.amigoscode.clients.notification; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; @FeignClient( name = "notification", url = "${clients.notification.url}" ) public interface NotificationClient { @PostMapping("api/v1/notification") void sendNotification(NotificationRequest notificationRequest); } ================================================ FILE: clients/src/main/java/com/amigoscode/clients/notification/NotificationRequest.java ================================================ package com.amigoscode.clients.notification; public record NotificationRequest( Integer toCustomerId, String toCustomerName, String message ) { } ================================================ FILE: clients/src/main/resources/clients-default.properties ================================================ clients.customer.url=http://localhost:8080 clients.fraud.url=http://localhost:8081 clients.notification.url=http://localhost:8082 ================================================ FILE: clients/src/main/resources/clients-docker.properties ================================================ clients.customer.url=http://customer:8080 clients.fraud.url=http://fraud:8081 clients.notification.url=http://notification:8082 ================================================ FILE: clients/src/main/resources/clients-kube.properties ================================================ clients.customer.url=http://customer:8080 clients.fraud.url=http://fraud:8081 clients.notification.url=http://notification:8082 ================================================ FILE: customer/pom.xml ================================================ com.amigoscode amigosservices 1.0-SNAPSHOT 4.0.0 jar customer org.springframework.boot spring-boot-maven-plugin build-docker-image com.google.cloud.tools jib-maven-plugin org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-jpa org.postgresql postgresql runtime org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-sleuth org.springframework.cloud spring-cloud-sleuth-zipkin org.springframework.boot spring-boot-starter-amqp com.amigoscode amqp 1.0-SNAPSHOT compile com.amigoscode clients 1.0-SNAPSHOT compile ================================================ FILE: customer/src/main/java/com/amigoscode/customer/Customer.java ================================================ package com.amigoscode.customer; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.SequenceGenerator; import javax.persistence.GenerationType; @Data @Builder @AllArgsConstructor @NoArgsConstructor @Entity public class Customer { @Id @SequenceGenerator( name = "customer_id_sequence", sequenceName = "customer_id_sequence" ) @GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "customer_id_sequence" ) private Integer id; private String firstName; private String lastName; private String email; } ================================================ FILE: customer/src/main/java/com/amigoscode/customer/CustomerApplication.java ================================================ package com.amigoscode.customer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySources; @SpringBootApplication( scanBasePackages = { "com.amigoscode.customer", "com.amigoscode.amqp", } ) @EnableEurekaClient @EnableFeignClients( basePackages = "com.amigoscode.clients" ) @PropertySources({ @PropertySource("classpath:clients-${spring.profiles.active}.properties") }) public class CustomerApplication { public static void main(String[] args) { SpringApplication.run(CustomerApplication.class, args); } } ================================================ FILE: customer/src/main/java/com/amigoscode/customer/CustomerController.java ================================================ package com.amigoscode.customer; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController @RequestMapping("api/v1/customers") @AllArgsConstructor public class CustomerController { private final CustomerService customerService; @PostMapping public void registerCustomer(@RequestBody CustomerRegistrationRequest customerRegistrationRequest) { log.info("new customer registration {}", customerRegistrationRequest); customerService.registerCustomer(customerRegistrationRequest); } } ================================================ FILE: customer/src/main/java/com/amigoscode/customer/CustomerRegistrationRequest.java ================================================ package com.amigoscode.customer; public record CustomerRegistrationRequest( String firstName, String lastName, String email) { } ================================================ FILE: customer/src/main/java/com/amigoscode/customer/CustomerRepository.java ================================================ package com.amigoscode.customer; import org.springframework.data.jpa.repository.JpaRepository; public interface CustomerRepository extends JpaRepository { } ================================================ FILE: customer/src/main/java/com/amigoscode/customer/CustomerService.java ================================================ package com.amigoscode.customer; import com.amigoscode.amqp.RabbitMQMessageProducer; import com.amigoscode.clients.fraud.FraudCheckResponse; import com.amigoscode.clients.fraud.FraudClient; import com.amigoscode.clients.notification.NotificationClient; import com.amigoscode.clients.notification.NotificationRequest; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; @Service @AllArgsConstructor public class CustomerService { private final CustomerRepository customerRepository; private final FraudClient fraudClient; private final RabbitMQMessageProducer rabbitMQMessageProducer; public void registerCustomer(CustomerRegistrationRequest request) { Customer customer = Customer.builder() .firstName(request.firstName()) .lastName(request.lastName()) .email(request.email()) .build(); // todo: check if email valid // todo: check if email not taken customerRepository.saveAndFlush(customer); FraudCheckResponse fraudCheckResponse = fraudClient.isFraudster(customer.getId()); if (fraudCheckResponse.isFraudster()) { throw new IllegalStateException("fraudster"); } NotificationRequest notificationRequest = new NotificationRequest( customer.getId(), customer.getEmail(), String.format("Hi %s, welcome to Amigoscode...", customer.getFirstName()) ); rabbitMQMessageProducer.publish( notificationRequest, "internal.exchange", "internal.notification.routing-key" ); } } ================================================ FILE: customer/src/main/resources/application-docker.yml ================================================ server: port: 8080 spring: application: name: customer datasource: password: password url: jdbc:postgresql://postgres:5432/customer username: amigoscode jpa: hibernate: ddl-auto: create-drop properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true show-sql: true zipkin: base-url: http://zipkin:9411 rabbitmq: addresses: rabbitmq:5672 eureka: client: service-url: defaultZone: http://eureka-server:8761/eureka fetch-registry: true register-with-eureka: true enabled: false ================================================ FILE: customer/src/main/resources/application-kube.yml ================================================ server: port: 8080 spring: application: name: customer datasource: password: password url: jdbc:postgresql://postgres:5432/customer username: amigoscode jpa: hibernate: ddl-auto: create-drop properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true show-sql: true zipkin: base-url: http://zipkin:9411 rabbitmq: addresses: rabbitmq:5672 eureka: client: service-url: defaultZone: http://eureka-server:8761/eureka fetch-registry: true register-with-eureka: true enabled: false ================================================ FILE: customer/src/main/resources/application.yml ================================================ server: port: 8080 spring: application: name: customer datasource: password: password url: jdbc:postgresql://localhost:5432/customer username: amigoscode jpa: hibernate: ddl-auto: create-drop properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true show-sql: true zipkin: base-url: http://localhost:9411 rabbitmq: addresses: localhost:5672 profiles: active: default eureka: client: service-url: defaultZone: http://localhost:8761/eureka fetch-registry: true register-with-eureka: true enabled: true ================================================ FILE: customer/src/main/resources/banner.txt ================================================ ,-----. ,--. ' .--./ ,--.,--. ,---. ,-' '-. ,---. ,--,--,--. ,---. ,--.--. | | | || | ( .-' '-. .-' | .-. | | | | .-. : | .--' ' '--'\ ' '' ' .-' `) | | ' '-' ' | | | | \ --. | | `-----' `----' `----' `--' `---' `--`--`--' `----' `--' ================================================ FILE: diagrams.drawio ================================================ ================================================ FILE: docker-compose.yml ================================================ services: postgres: container_name: postgres image: postgres environment: POSTGRES_USER: amigoscode POSTGRES_PASSWORD: password PGDATA: /data/postgres volumes: - postgres:/data/postgres ports: - "5432:5432" networks: - postgres restart: unless-stopped pgadmin: container_name: pgadmin image: dpage/pgadmin4 environment: PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org} PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin} PGADMIN_CONFIG_SERVER_MODE: 'False' volumes: - pgadmin:/var/lib/pgadmin ports: - "5050:80" networks: - postgres restart: unless-stopped zipkin: image: openzipkin/zipkin container_name: zipkin ports: - "9411:9411" networks: - spring rabbitmq: image: rabbitmq:3.9.11-management-alpine container_name: rabbitmq ports: - "5672:5672" - "15672:15672" networks: - spring # eureka-server: # image: amigoscode/eureka-server:latest # container_name: eureka-server # ports: # - "8761:8761" # environment: # - SPRING_PROFILES_ACTIVE=docker # networks: # - spring # depends_on: # - zipkin # apigw: # image: amigoscode/apigw:latest # container_name: apigw # ports: # - "8083:8083" # environment: # - SPRING_PROFILES_ACTIVE=docker # networks: # - spring # depends_on: # - zipkin # - eureka-server # customer: # image: amigoscode/customer:latest # container_name: customer # ports: # - "8080:8080" # environment: # - SPRING_PROFILES_ACTIVE=docker # networks: # - spring # - postgres # depends_on: # - zipkin # - postgres # - rabbitmq # fraud: # image: amigoscode/fraud:latest # container_name: fraud # ports: # - "8081:8081" # environment: # - SPRING_PROFILES_ACTIVE=docker # networks: # - spring # - postgres # depends_on: # - zipkin # - postgres # - rabbitmq # notification: # image: amigoscode/notification:latest # container_name: notification # ports: # - "8082:8082" # environment: # - SPRING_PROFILES_ACTIVE=docker # networks: # - spring # - postgres # depends_on: # - zipkin # - postgres # - rabbitmq networks: postgres: driver: bridge spring: driver: bridge volumes: postgres: pgadmin: ================================================ FILE: eureka-server/pom.xml ================================================ amigosservices com.amigoscode 1.0-SNAPSHOT 4.0.0 jar eureka-server org.springframework.boot spring-boot-maven-plugin build-docker-image com.google.cloud.tools jib-maven-plugin org.springframework.cloud spring-cloud-starter-netflix-eureka-server org.springframework.cloud spring-cloud-starter-sleuth org.springframework.cloud spring-cloud-sleuth-zipkin ================================================ FILE: eureka-server/src/main/java/com/amigoscode/eurekaserver/EurekaServerApplication.java ================================================ package com.amigoscode.eurekaserver; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } } ================================================ FILE: eureka-server/src/main/resources/application-docker.yml ================================================ spring: application: name: eureka-server zipkin: base-url: http://zipkin:9411 server: port: 8761 eureka: client: fetch-registry: false register-with-eureka: false ================================================ FILE: eureka-server/src/main/resources/application.yml ================================================ spring: application: name: eureka-server zipkin: base-url: http://localhost:9411 server: port: 8761 eureka: client: fetch-registry: false register-with-eureka: false ================================================ FILE: eureka-server/src/main/resources/banner.txt ================================================ ,------. ,--. ,---. | .---' ,--.,--. ,--.--. ,---. | |,-. ,--,--. ' .-' ,---. ,--.--. ,--. ,--. ,---. ,--.--. | `--, | || | | .--' | .-. : | / ' ,-. | `. `-. | .-. : | .--' \ `' / | .-. : | .--' | `---. ' '' ' | | \ --. | \ \ \ '-' | .-' | \ --. | | \ / \ --. | | `------' `----' `--' `----' `--'`--' `--`--' `-----' `----' `--' `--' `----' `--' ================================================ FILE: fraud/pom.xml ================================================ amigosservices com.amigoscode 1.0-SNAPSHOT 4.0.0 jar fraud org.springframework.boot spring-boot-maven-plugin build-docker-image com.google.cloud.tools jib-maven-plugin org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-jpa org.postgresql postgresql runtime org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-sleuth org.springframework.cloud spring-cloud-sleuth-zipkin com.amigoscode clients 1.0-SNAPSHOT compile ================================================ FILE: fraud/src/main/java/com/amigoscode/fraud/FraudApplication.java ================================================ package com.amigoscode.fraud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySources; @SpringBootApplication @EnableEurekaClient @PropertySources({ @PropertySource("classpath:clients-${spring.profiles.active}.properties") }) public class FraudApplication { public static void main(String[] args) { SpringApplication.run(FraudApplication.class, args); } } ================================================ FILE: fraud/src/main/java/com/amigoscode/fraud/FraudCheckHistory.java ================================================ package com.amigoscode.fraud; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.SequenceGenerator; import javax.persistence.GenerationType; import java.time.LocalDateTime; @Data @Builder @AllArgsConstructor @NoArgsConstructor @Entity public class FraudCheckHistory { @Id @SequenceGenerator( name = "fraud_id_sequence", sequenceName = "fraud_id_sequence" ) @GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "fraud_id_sequence" ) private Integer id; private Integer customerId; private Boolean isFraudster; private LocalDateTime createdAt; } ================================================ FILE: fraud/src/main/java/com/amigoscode/fraud/FraudCheckHistoryRepository.java ================================================ package com.amigoscode.fraud; import org.springframework.data.jpa.repository.JpaRepository; public interface FraudCheckHistoryRepository extends JpaRepository { } ================================================ FILE: fraud/src/main/java/com/amigoscode/fraud/FraudCheckService.java ================================================ package com.amigoscode.fraud; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @Service @AllArgsConstructor public class FraudCheckService { private final FraudCheckHistoryRepository fraudCheckHistoryRepository; public boolean isFraudulentCustomer(Integer customerId) { fraudCheckHistoryRepository.save( FraudCheckHistory.builder() .customerId(customerId) .isFraudster(false) .createdAt(LocalDateTime.now()) .build() ); return false; } } ================================================ FILE: fraud/src/main/java/com/amigoscode/fraud/FraudController.java ================================================ package com.amigoscode.fraud; import com.amigoscode.clients.fraud.FraudCheckResponse; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("api/v1/fraud-check") @AllArgsConstructor @Slf4j public class FraudController { private final FraudCheckService fraudCheckService; @GetMapping(path = "{customerId}") public FraudCheckResponse isFraudster( @PathVariable("customerId") Integer customerId) { boolean isFraudulentCustomer = fraudCheckService. isFraudulentCustomer(customerId); log.info("fraud check request for customer {}", customerId); return new FraudCheckResponse(isFraudulentCustomer); } } ================================================ FILE: fraud/src/main/resources/application-docker.yml ================================================ server: port: 8081 spring: application: name: fraud datasource: password: password url: jdbc:postgresql://postgres:5432/fraud username: amigoscode jpa: hibernate: ddl-auto: create-drop properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true show-sql: true zipkin: base-url: http://zipkin:9411 eureka: client: service-url: defaultZone: http://eureka-server:8761/eureka fetch-registry: true register-with-eureka: true enabled: false ================================================ FILE: fraud/src/main/resources/application-kube.yml ================================================ server: port: 8081 spring: application: name: fraud datasource: password: password url: jdbc:postgresql://postgres:5432/fraud username: amigoscode jpa: hibernate: ddl-auto: create-drop properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true show-sql: true zipkin: base-url: http://zipkin:9411 eureka: client: service-url: defaultZone: http://eureka-server:8761/eureka fetch-registry: true register-with-eureka: true enabled: false ================================================ FILE: fraud/src/main/resources/application.yml ================================================ server: port: 8081 spring: application: name: fraud datasource: password: password url: jdbc:postgresql://localhost:5432/fraud username: amigoscode jpa: hibernate: ddl-auto: create-drop properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true show-sql: true zipkin: base-url: http://localhost:9411 profiles: active: default eureka: client: service-url: defaultZone: http://localhost:8761/eureka fetch-registry: true register-with-eureka: true enabled: true ================================================ FILE: fraud/src/main/resources/banner.txt ================================================ ,------. ,--. | .---' ,--.--. ,--,--. ,--.,--. ,-| | | `--, | .--' ' ,-. | | || | ' .-. | | |` | | \ '-' | ' '' ' \ `-' | `--' `--' `--`--' `----' `---' ================================================ FILE: k8s/minikube/bootstrap/postgres/configmap.yml ================================================ apiVersion: v1 kind: ConfigMap metadata: name: postgres-config data: POSTGRES_DB: amigoscode POSTGRES_USER: amigoscode POSTGRES_PASSWORD: password ================================================ FILE: k8s/minikube/bootstrap/postgres/service.yml ================================================ apiVersion: v1 kind: Service metadata: name: postgres spec: selector: app: postgres ports: - port: 5432 targetPort: 5432 type: ClusterIP ================================================ FILE: k8s/minikube/bootstrap/postgres/statefulset.yml ================================================ apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres labels: app: postgres spec: serviceName: postgres replicas: 1 template: metadata: name: postgres labels: app: postgres spec: volumes: - name: postgres persistentVolumeClaim: claimName: postgres-pc-volume-claim containers: - name: postgres image: postgres imagePullPolicy: IfNotPresent volumeMounts: - mountPath: "/var/lib/postgresql/data" name: postgres envFrom: - configMapRef: name: postgres-config resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 512Mi restartPolicy: Always selector: matchLabels: app: postgres ================================================ FILE: k8s/minikube/bootstrap/postgres/volume.yml ================================================ apiVersion: v1 kind: PersistentVolume metadata: name: postgres-pc-volume labels: type: local app: postgres spec: storageClassName: manual capacity: storage: 5Gi accessModes: - ReadWriteMany hostPath: path: /mnt/data --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: postgres-pc-volume-claim labels: app: postgres spec: storageClassName: manual accessModes: - ReadWriteMany resources: requests: storage: 5Gi ================================================ FILE: k8s/minikube/bootstrap/rabbitmq/README.md ================================================ # Deploy RabbitMQ on Kubernetes with the Kubernetes Peer Discovery Plugin to Minikube This is an **example** that demonstrates a RabbitMQ deployment on Kubernetes with peer discovery via `rabbitmq-peer-discovery-k8s` plugin. ## Production (Non-)Suitability Some values in this example **may or may not be optimal for your deployment**. We encourage users to get familiar with the [RabbitMQ Peer Discovery guide](https://www.rabbitmq.com/cluster-formation.html), [RabbitMQ Production Checklist](https://www.rabbitmq.com/production-checklist.html) and the rest of [RabbitMQ documentation](https://www.rabbitmq.com/documentation.html) before going into production. Having [metrics](https://www.rabbitmq.com/monitoring.html), both of RabbitMQ and applications that use it, is critically important when making informed decisions about production systems. ## Pre-requisites The example uses, targets or assumes: * [Minikube](https://kubernetes.io/docs/setup/learning-environment/minikube/) with the [VirtualBox](https://www.virtualbox.org/) driver (other drivers can be used, too) * Kubernetes 1.6 * RabbitMQ [Docker image](https://hub.docker.com/_/rabbitmq/) (maintained [by Docker, Inc](https://hub.docker.com/_/rabbitmq/)) * A [StatefulSets controller](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/) ## Quick Start with Make This example comes with a Make target that sets up VirtualBox, Minikube and an example cluster in a single command. It can be found under this directory. [Homebrew](https://brew.sh/) will be used to install packages and on macOS, VirtualBox [will need OS permissions to install its kernel module](https://developer.apple.com/library/archive/technotes/tn2459/_index.html). The Homebrew cask installer will ask for your password at some point with a prompt that looks like this: ``` Changing ownership of paths required by virtualbox; your password may be necessary ``` Please inspect the Make file to be extra sure that you understand and agree to what it does. After enabling 3rd party kernel extensions in OS setings, run the default Make target in this directory: ``` make ``` which is equivalent to first running ``` make start-minikube ``` to install VirtualBox and Minikube using Homebrew, then ``` make run-in-minikube ``` to start Minikube and `kubectl apply` the example, and finally ``` make wait-for-rabbitmq ``` to wait for cluster formation. Once the changes are applied, follow the steps in the Check Cluster Status section below. In case you would prefer to install and run Minikube manually, see the following few sections. ## Running the Example Manually with Minikube ### Preresuites * Make sure that VirtualBox is installed * Install [`minikube`](https://kubernetes.io/docs/tasks/tools/install-minikube/) and start it with `--vm-driver=virtualbox` * Install [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/) ### Start Minikube Start a `minikube` virtual machine: ``` sh minikube start --cpus=2 --memory=2040 --disk-size "10 GB" --vm-driver=virtualbox ``` ### Create a Namespace Create a Kubernetes namespace for RabbitMQ tests: ``` sh kubectl create namespace test-rabbitmq ``` ### Set Up Kubernetes Permissions In Kubernetes 1.6 or above, RBAC authorization is enabled by default. This example configures RBAC related bits so that the peer discovery plugin is allowed to access the nodes information it needs. The `ServiceAccount` and `Role` resources will be created in the following step. ### kubectl Apply Things Deploy the config map, services, a stateful set and so on: ``` sh # will apply all files under this directory kubectl create -f minikube ``` ### Check Cluster Status Wait for a a few minutes for pods to start. Since this example uses a stateful set with ordered startup, the pods will be started one by one. To monitor pod startup process, use ``` sh kubectl --namespace="test-rabbitmq" get pods ``` To run `rabbitmq-diagnostics cluster_status`: ``` sh FIRST_POD=$(kubectl get pods --namespace test-rabbitmq -l 'app=rabbitmq' -o jsonpath='{.items[0].metadata.name }') kubectl exec --namespace=test-rabbitmq $FIRST_POD -- rabbitmq-diagnostics cluster_status ``` to check cluster status. Note that nodes can take some time to start and discover each other. The output should look something like this: ``` Cluster status of node rabbit@rabbitmq-0.rabbitmq.test-rabbitmq.svc.cluster.local ... Basics Cluster name: rabbit@rabbitmq-0.rabbitmq.test-rabbitmq.svc.cluster.local Disk Nodes rabbit@rabbitmq-0.rabbitmq.test-rabbitmq.svc.cluster.local rabbit@rabbitmq-1.rabbitmq.test-rabbitmq.svc.cluster.local rabbit@rabbitmq-2.rabbitmq.test-rabbitmq.svc.cluster.local Running Nodes rabbit@rabbitmq-0.rabbitmq.test-rabbitmq.svc.cluster.local rabbit@rabbitmq-1.rabbitmq.test-rabbitmq.svc.cluster.local rabbit@rabbitmq-2.rabbitmq.test-rabbitmq.svc.cluster.local Versions rabbit@rabbitmq-0.rabbitmq.test-rabbitmq.svc.cluster.local: RabbitMQ 3.8.1 on Erlang 22.1.8 rabbit@rabbitmq-1.rabbitmq.test-rabbitmq.svc.cluster.local: RabbitMQ 3.8.1 on Erlang 22.1.8 rabbit@rabbitmq-2.rabbitmq.test-rabbitmq.svc.cluster.local: RabbitMQ 3.8.1 on Erlang 22.1.8 Alarms (none) Network Partitions (none) Listeners Node: rabbit@rabbitmq-0.rabbitmq.test-rabbitmq.svc.cluster.local, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication Node: rabbit@rabbitmq-0.rabbitmq.test-rabbitmq.svc.cluster.local, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0 Node: rabbit@rabbitmq-0.rabbitmq.test-rabbitmq.svc.cluster.local, interface: [::], port: 15672, protocol: http, purpose: HTTP API Node: rabbit@rabbitmq-1.rabbitmq.test-rabbitmq.svc.cluster.local, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication Node: rabbit@rabbitmq-1.rabbitmq.test-rabbitmq.svc.cluster.local, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0 Node: rabbit@rabbitmq-1.rabbitmq.test-rabbitmq.svc.cluster.local, interface: [::], port: 15672, protocol: http, purpose: HTTP API Feature flags Flag: drop_unroutable_metric, state: enabled Flag: empty_basic_get_metric, state: enabled Flag: implicit_default_bindings, state: enabled Flag: quorum_queue, state: enabled Flag: virtual_host_metadata, state: enabled ``` ### Use Public Minikube IP Address to Connect Get the public `minikube` VM IP address: ``` sh minikube ip # => 192.168.99.104 ``` The [ports used](https://www.rabbitmq.com/networking.html#ports) by this example are: * `amqp://guest:guest@{minikube_ip}:30672`: [AMQP 0-9-1 and AMQP 1.0](https://www.rabbitmq.com/networking.html#ports) client connections * `http://{minikube_ip}:31672`: [HTTP API and management UI](https://www.rabbitmq.com/management.html) ### Scaling the Number of RabbitMQ Cluster Nodes (Kubernetes Pod Replicas) ``` sh # Odd numbers of nodes are necessary for a clear quorum: 3, 5, 7 and so on kubectl scale statefulset/rabbitmq --namespace=test-rabbitmq --replicas=5 ``` ================================================ FILE: k8s/minikube/bootstrap/rabbitmq/configmap.yaml ================================================ apiVersion: v1 kind: ConfigMap metadata: name: rabbitmq-config data: enabled_plugins: | [rabbitmq_management,rabbitmq_peer_discovery_k8s]. rabbitmq.conf: | ## Cluster formation. See https://www.rabbitmq.com/cluster-formation.html to learn more. cluster_formation.peer_discovery_backend = rabbit_peer_discovery_k8s cluster_formation.k8s.host = kubernetes.default.svc.cluster.local ## Should RabbitMQ node name be computed from the pod's hostname or IP address? ## IP addresses are not stable, so using [stable] hostnames is recommended when possible. ## Set to "hostname" to use pod hostnames. ## When this value is changed, so should the variable used to set the RABBITMQ_NODENAME ## environment variable. cluster_formation.k8s.address_type = hostname ## How often should node cleanup checks run? cluster_formation.node_cleanup.interval = 30 ## Set to false if automatic removal of unknown/absent nodes ## is desired. This can be dangerous, see ## * https://www.rabbitmq.com/cluster-formation.html#node-health-checks-and-cleanup ## * https://groups.google.com/forum/#!msg/rabbitmq-users/wuOfzEywHXo/k8z_HWIkBgAJ cluster_formation.node_cleanup.only_log_warning = true cluster_partition_handling = autoheal ## See https://www.rabbitmq.com/ha.html#master-migration-data-locality queue_master_locator=min-masters ## This is just an example. ## This enables remote access for the default user with well known credentials. ## Consider deleting the default user and creating a separate user with a set of generated ## credentials instead. ## Learn more at https://www.rabbitmq.com/access-control.html#loopback-users loopback_users.guest = false ================================================ FILE: k8s/minikube/bootstrap/rabbitmq/rbac.yaml ================================================ --- apiVersion: v1 kind: ServiceAccount metadata: name: rabbitmq --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: rabbitmq-peer-discovery-rbac rules: - apiGroups: [""] resources: ["endpoints"] verbs: ["get"] - apiGroups: [""] resources: ["events"] verbs: ["create"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: rabbitmq-peer-discovery-rbac subjects: - kind: ServiceAccount name: rabbitmq roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: rabbitmq-peer-discovery-rbac ================================================ FILE: k8s/minikube/bootstrap/rabbitmq/services.yaml ================================================ kind: Service apiVersion: v1 metadata: name: rabbitmq labels: app: rabbitmq type: LoadBalancer spec: type: NodePort ports: - name: http protocol: TCP port: 15672 targetPort: 15672 nodePort: 31672 - name: amqp protocol: TCP port: 5672 targetPort: 5672 nodePort: 30672 selector: app: rabbitmq ================================================ FILE: k8s/minikube/bootstrap/rabbitmq/statefulset.yaml ================================================ apiVersion: apps/v1 # See the Prerequisites section of https://www.rabbitmq.com/cluster-formation.html#peer-discovery-k8s. kind: StatefulSet metadata: name: rabbitmq spec: serviceName: rabbitmq # Three nodes is the recommended minimum. Some features may require a majority of nodes # to be available. replicas: 1 selector: matchLabels: app: rabbitmq template: metadata: labels: app: rabbitmq spec: serviceAccountName: rabbitmq terminationGracePeriodSeconds: 10 nodeSelector: # Use Linux nodes in a mixed OS kubernetes cluster. # Learn more at https://kubernetes.io/docs/reference/kubernetes-api/labels-annotations-taints/#kubernetes-io-os kubernetes.io/os: linux containers: - name: rabbitmq-k8s image: rabbitmq:3.8 volumeMounts: - name: config-volume mountPath: /etc/rabbitmq # Learn more about what ports various protocols use # at https://www.rabbitmq.com/networking.html#ports ports: - name: http protocol: TCP containerPort: 15672 - name: amqp protocol: TCP containerPort: 5672 livenessProbe: exec: # This is just an example. There is no "one true health check" but rather # several rabbitmq-diagnostics commands that can be combined to form increasingly comprehensive # and intrusive health checks. # Learn more at https://www.rabbitmq.com/monitoring.html#health-checks. # # Stage 2 check: command: ["rabbitmq-diagnostics", "status"] initialDelaySeconds: 60 # See https://www.rabbitmq.com/monitoring.html for monitoring frequency recommendations. periodSeconds: 60 timeoutSeconds: 15 readinessProbe: exec: # This is just an example. There is no "one true health check" but rather # several rabbitmq-diagnostics commands that can be combined to form increasingly comprehensive # and intrusive health checks. # Learn more at https://www.rabbitmq.com/monitoring.html#health-checks. # # Stage 1 check: command: ["rabbitmq-diagnostics", "ping"] initialDelaySeconds: 20 periodSeconds: 60 timeoutSeconds: 10 imagePullPolicy: Always env: - name: MY_POD_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.name - name: MY_POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: RABBITMQ_USE_LONGNAME value: "true" # See a note on cluster_formation.k8s.address_type in the config file section - name: K8S_SERVICE_NAME value: rabbitmq - name: RABBITMQ_NODENAME value: rabbit@$(MY_POD_NAME).$(K8S_SERVICE_NAME).$(MY_POD_NAMESPACE).svc.cluster.local - name: K8S_HOSTNAME_SUFFIX value: .$(K8S_SERVICE_NAME).$(MY_POD_NAMESPACE).svc.cluster.local - name: RABBITMQ_ERLANG_COOKIE value: "mycookie" volumes: - name: config-volume configMap: name: rabbitmq-config items: - key: rabbitmq.conf path: rabbitmq.conf - key: enabled_plugins path: enabled_plugins ================================================ FILE: k8s/minikube/bootstrap/zipkin/service.yml ================================================ apiVersion: v1 kind: Service metadata: name: zipkin spec: selector: app: zipkin ports: - port: 9411 targetPort: 9411 protocol: TCP type: LoadBalancer ================================================ FILE: k8s/minikube/bootstrap/zipkin/statefulset.yml ================================================ apiVersion: apps/v1 kind: StatefulSet metadata: name: zipkin labels: app: zipkin spec: serviceName: zipkin replicas: 1 template: metadata: name: zipkin labels: app: zipkin spec: containers: - name: zipkin image: openzipkin/zipkin imagePullPolicy: Always ports: - containerPort: 9411 protocol: TCP resources: requests: cpu: 100m memory: 256Mi limits: cpu: 200m memory: 256Mi restartPolicy: Always selector: matchLabels: app: zipkin ================================================ FILE: k8s/minikube/services/customer/deployment.yml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: customer labels: app: customer spec: replicas: 1 template: metadata: name: customer labels: app: customer spec: containers: - name: customer image: amigoscode/customer:latest imagePullPolicy: Always ports: - containerPort: 8080 env: - name: SPRING_PROFILES_ACTIVE value: kube restartPolicy: Always selector: matchLabels: app: customer ================================================ FILE: k8s/minikube/services/customer/service.yml ================================================ apiVersion: v1 kind: Service metadata: name: customer spec: selector: app: customer ports: - port: 80 targetPort: 8080 type: LoadBalancer ================================================ FILE: k8s/minikube/services/fraud/deployment.yml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: fraud labels: app: fraud spec: replicas: 1 template: metadata: name: fraud labels: app: fraud spec: containers: - name: fraud image: amigoscode/fraud:latest imagePullPolicy: Always ports: - containerPort: 8081 env: - name: SPRING_PROFILES_ACTIVE value: kube restartPolicy: Always selector: matchLabels: app: fraud ================================================ FILE: k8s/minikube/services/fraud/service.yml ================================================ apiVersion: v1 kind: Service metadata: name: fraud spec: selector: app: fraud ports: - port: 80 targetPort: 8081 type: NodePort ================================================ FILE: k8s/minikube/services/notification/deployment.yml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: notification labels: app: notification spec: replicas: 1 template: metadata: name: notification labels: app: notification spec: containers: - name: notification image: amigoscode/notification:latest imagePullPolicy: Always ports: - containerPort: 8083 env: - name: SPRING_PROFILES_ACTIVE value: kube restartPolicy: Always selector: matchLabels: app: notification ================================================ FILE: k8s/minikube/services/notification/service.yml ================================================ apiVersion: v1 kind: Service metadata: name: notification spec: selector: app: notification ports: - port: 80 targetPort: 8082 type: NodePort ================================================ FILE: notification/pom.xml ================================================ amigosservices com.amigoscode 1.0-SNAPSHOT 4.0.0 jar notification org.springframework.boot spring-boot-maven-plugin build-docker-image com.google.cloud.tools jib-maven-plugin org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-jpa org.postgresql postgresql runtime org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-sleuth org.springframework.cloud spring-cloud-sleuth-zipkin org.springframework.boot spring-boot-starter-amqp com.amigoscode amqp 1.0-SNAPSHOT compile com.amigoscode clients 1.0-SNAPSHOT compile ================================================ FILE: notification/src/main/java/com/amigoscode/notification/Notification.java ================================================ package com.amigoscode.notification; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import javax.persistence.*; import java.time.LocalDateTime; @Entity @Builder @AllArgsConstructor @NoArgsConstructor @Getter @Setter @ToString public class Notification { @Id @SequenceGenerator( name = "notification_id_sequence", sequenceName = "notification_id_sequence" ) @GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "notification_id_sequence" ) private Integer notificationId; private Integer toCustomerId; private String toCustomerEmail; private String sender; private String message; private LocalDateTime sentAt; } ================================================ FILE: notification/src/main/java/com/amigoscode/notification/NotificationApplication.java ================================================ package com.amigoscode.notification; import com.amigoscode.amqp.RabbitMQMessageProducer; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySources; @SpringBootApplication( scanBasePackages = { "com.amigoscode.notification", "com.amigoscode.amqp", } ) @PropertySources({ @PropertySource("classpath:clients-${spring.profiles.active}.properties") }) public class NotificationApplication { public static void main(String[] args) { SpringApplication.run(NotificationApplication.class, args); } // @Bean // CommandLineRunner commandLineRunner( // RabbitMQMessageProducer producer, // NotificationConfig notificationConfig // ) { // return args -> { // producer.publish( // new Person("Ali", 18), // notificationConfig.getInternalExchange(), // notificationConfig.getInternalNotificationRoutingKey()); // }; // } // // record Person(String name, int age){} } ================================================ FILE: notification/src/main/java/com/amigoscode/notification/NotificationConfig.java ================================================ package com.amigoscode.notification; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.TopicExchange; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class NotificationConfig { @Value("${rabbitmq.exchanges.internal}") private String internalExchange; @Value("${rabbitmq.queues.notification}") private String notificationQueue; @Value("${rabbitmq.routing-keys.internal-notification}") private String internalNotificationRoutingKey; @Bean public TopicExchange internalTopicExchange() { return new TopicExchange(this.internalExchange); } @Bean public Queue notificationQueue() { return new Queue(this.notificationQueue); } @Bean public Binding internalToNotificationBinding() { return BindingBuilder .bind(notificationQueue()) .to(internalTopicExchange()) .with(this.internalNotificationRoutingKey); } public String getInternalExchange() { return internalExchange; } public String getNotificationQueue() { return notificationQueue; } public String getInternalNotificationRoutingKey() { return internalNotificationRoutingKey; } } ================================================ FILE: notification/src/main/java/com/amigoscode/notification/NotificationController.java ================================================ package com.amigoscode.notification; import com.amigoscode.clients.notification.NotificationRequest; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("api/v1/notification") @AllArgsConstructor @Slf4j public class NotificationController { private final NotificationService notificationService; @PostMapping public void sendNotification(@RequestBody NotificationRequest notificationRequest) { log.info("New notification... {}", notificationRequest); notificationService.send(notificationRequest); } } ================================================ FILE: notification/src/main/java/com/amigoscode/notification/NotificationRepository.java ================================================ package com.amigoscode.notification; import org.springframework.data.jpa.repository.JpaRepository; public interface NotificationRepository extends JpaRepository { } ================================================ FILE: notification/src/main/java/com/amigoscode/notification/NotificationService.java ================================================ package com.amigoscode.notification; import com.amigoscode.clients.notification.NotificationRequest; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @Service @AllArgsConstructor public class NotificationService { private final NotificationRepository notificationRepository; public void send(NotificationRequest notificationRequest) { notificationRepository.save( Notification.builder() .toCustomerId(notificationRequest.toCustomerId()) .toCustomerEmail(notificationRequest.toCustomerName()) .sender("Amigoscode") .message(notificationRequest.message()) .sentAt(LocalDateTime.now()) .build() ); } } ================================================ FILE: notification/src/main/java/com/amigoscode/notification/rabbitmq/NotificationConsumer.java ================================================ package com.amigoscode.notification.rabbitmq; import com.amigoscode.clients.notification.NotificationRequest; import com.amigoscode.notification.NotificationService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component @AllArgsConstructor @Slf4j public class NotificationConsumer { private final NotificationService notificationService; @RabbitListener(queues = "${rabbitmq.queues.notification}") public void consumer(NotificationRequest notificationRequest) { log.info("Consumed {} from queue", notificationRequest); notificationService.send(notificationRequest); } } ================================================ FILE: notification/src/main/resources/application-docker.yml ================================================ server: port: 8082 spring: application: name: notification datasource: password: password url: jdbc:postgresql://postgres:5432/notification username: amigoscode jpa: hibernate: ddl-auto: create-drop properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true show-sql: true zipkin: base-url: http://zipkin:9411 rabbitmq: addresses: rabbitmq:5672 eureka: client: service-url: defaultZone: http://eureka-server:8761/eureka fetch-registry: true register-with-eureka: true enabled: false rabbitmq: exchanges: internal: internal.exchange queues: notification: notification.queue routing-keys: internal-notification: internal.notification.routing-key ================================================ FILE: notification/src/main/resources/application-kube.yml ================================================ server: port: 8082 spring: application: name: notification datasource: password: password url: jdbc:postgresql://postgres:5432/notification username: amigoscode jpa: hibernate: ddl-auto: create-drop properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true show-sql: true zipkin: base-url: http://zipkin:9411 rabbitmq: addresses: rabbitmq:5672 eureka: client: service-url: defaultZone: http://eureka-server:8761/eureka fetch-registry: true register-with-eureka: true enabled: false rabbitmq: exchanges: internal: internal.exchange queues: notification: notification.queue routing-keys: internal-notification: internal.notification.routing-key ================================================ FILE: notification/src/main/resources/application.yml ================================================ server: port: 8082 spring: application: name: notification datasource: password: password url: jdbc:postgresql://localhost:5432/notification username: amigoscode jpa: hibernate: ddl-auto: create-drop properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true show-sql: true zipkin: base-url: http://localhost:9411 rabbitmq: addresses: localhost:5672 profiles: active: default eureka: client: service-url: defaultZone: http://localhost:8761/eureka fetch-registry: true register-with-eureka: true enabled: true rabbitmq: exchanges: internal: internal.exchange queues: notification: notification.queue routing-keys: internal-notification: internal.notification.routing-key ================================================ FILE: notification/src/main/resources/banner.txt ================================================ ,--. ,--. ,--. ,--. ,---. ,--. ,--. ,--. | ,'.| | ,---. ,-' '-. `--' / .-' `--' ,---. ,--,--. ,-' '-. `--' ,---. ,--,--, | |' ' | | .-. | '-. .-' ,--. | `-, ,--. | .--' ' ,-. | '-. .-' ,--. | .-. | | \ | | ` | ' '-' ' | | | | | .-' | | \ `--. \ '-' | | | | | ' '-' ' | || | `--' `--' `---' `--' `--' `--' `--' `---' `--`--' `--' `--' `---' `--''--' ${application.title} ${application.version} Powered by Spring Boot ${spring-boot.version} ================================================ FILE: pom.xml ================================================ 4.0.0 com.amigoscode amigosservices pom 1.0-SNAPSHOT customer fraud eureka-server clients notification apigw amqp amigosservices https://www.amigoscode.com UTF-8 17 17 2.5.7 2.5.7 2020.0.3 amigoscode/${project.artifactId}:${project.version} org.springframework.boot spring-boot-dependencies ${spring.boot.dependencies.version} import pom org.springframework.cloud spring-cloud-dependencies ${spring.cloud-version} pom import org.projectlombok lombok org.springframework.boot spring-boot-starter-test org.springframework.cloud spring-cloud-starter-openfeign org.springframework.boot spring-boot-maven-plugin ${spring.boot.maven.plugin.version} repackage com.google.cloud.tools jib-maven-plugin 3.1.4 eclipse-temurin:17@sha256:2b47a8ea946ce1e5365a1562414ed576e378b7b670cadff3fb98ebecf2890cdc arm64 linux amd64 linux latest package build org.apache.maven.plugins maven-compiler-plugin 3.8.0 17 17