{"id":26092895,"url":"https://github.com/jazzshu/microservices-communication","last_synced_at":"2026-04-11T17:02:31.879Z","repository":{"id":143113480,"uuid":"591046492","full_name":"jazzshu/microservices-communication","owner":"jazzshu","description":"Demo project to illustrate how microservices can communicate between each other","archived":false,"fork":false,"pushed_at":"2023-01-25T12:54:28.000Z","size":134,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-24T09:32:11.114Z","etag":null,"topics":["apache-kafka","docker","docker-compose","eureka","microservices","microservices-architecture","rabbitmq","rest-template","spring"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jazzshu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-19T20:03:40.000Z","updated_at":"2023-01-25T12:49:02.000Z","dependencies_parsed_at":null,"dependency_job_id":"2aab3be5-2722-46f6-9dd5-916596eebd4b","html_url":"https://github.com/jazzshu/microservices-communication","commit_stats":null,"previous_names":["jazzshu/microservices-communication"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzshu%2Fmicroservices-communication","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzshu%2Fmicroservices-communication/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzshu%2Fmicroservices-communication/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzshu%2Fmicroservices-communication/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jazzshu","download_url":"https://codeload.github.com/jazzshu/microservices-communication/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242679511,"owners_count":20168163,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apache-kafka","docker","docker-compose","eureka","microservices","microservices-architecture","rabbitmq","rest-template","spring"],"created_at":"2025-03-09T11:10:21.578Z","updated_at":"2025-12-05T17:02:27.829Z","avatar_url":"https://github.com/jazzshu.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Microservice communication\n\nFor microservices to communicate between them we have multiple options.\nHere we explore 3 of these methods.\n\nFirst you need at least 2 microservices that could run as a standalone application.\nIn this case we created a Customer microservice and a Fraud microservice.\n\nThe Customer microservice saves a new Customer object to a Postgres DB when hitting the \"api/v1/customers\" endpoint.\n\nAt that point we want to retrieve the newly saved Customer Id and use it to save a boolean value (*isFraudster*) to the\n*fraud_check_history* table. The saving of this value in the table is done on the Fraud microservice.\n\nTo do this we need to communicate to the Fraud microservice the *customerId* after the Customer microservice has saved it \nin its table.\n\nTo boot everything up we use a docker-compose script: this yml file, creates a Postgres instance running at port\n5432, and also adds to it the *pgadmin* image to have UI access to our DB running on port 5050. \n\nSo when running \n```shell\ndocker compose up -d\n```\nopen the web at *localhost:5050*, insert \"password\" as the master password, add a new Server, call it \"amigoscode\", and\nas the host use \"postgres\".\n\nTo access tables go to Servers -\u003e amigoscode -\u003e Databases -\u003e \u003ctableName\u003e -\u003e Schemas -\u003e public -\u003e tables.\n\n## RestTemplate\nTo communicate between microservices using RestTemplate you first of all need to instantiate a RestTemplate Bean in a config file.\n```java\n\n@Configuration\npublic class CustomerConfig {\n\n    @Bean\n    public RestTemplate restTemplate() {\n        return new RestTemplate();\n    }\n}\n```\nNext up, at the Service level of your \"producer\" class, in this case, CustomerService, we inject the RestTemplate bean,\nwe then use the *.getForObject* method of RestTemplate to make a GET request to our Fraud microservice, passing in the \nhost and port of the \"consumer\". \n```java\npublic record CustomerService(CustomerRepository customerRepository) {\n    public void registerCustomer(CustomerRegistrationRequest request) {\n        Customer customer = Customer.builder()\n                .firstName(request.firstName())\n                .lastName(request.lastName())\n                .email(request.email())\n                .build();\n        customerRepository.saveAndFlush(customer);\n        FraudCheckResponse response = restTemplate.getForObject(\n                \"http://localhost:8081/api/v1/fraud-check/{customerId}\",\n                FraudCheckResponse.class,\n                customer.getId()\n        );\n        if (response.isFraudster()) {\n            throw new IllegalStateException(\"fraudster\");\n        }\n\n        customerRepository.save(customer);\n    }\n}\n```\nAs you can see, you need to define the endpoint we want to hit, as well as the port: *http://localhost:8081/api/v1/fraud-check/{customerId}*.\nThis isn't very well suited for example when we will have multiple instances of the Fraud microservices, in which case we would need to implement some type of\nLoad Balancer to choose between which instance to hit.\n\nTo avoid having to hard-code host and port number you might as well use Spring Eureka as the Service Registry. Basically create a new microservice that holds the responsibility\nof a service registry, annotate it with @EnableEurekaServer and add some configuration properties. Then add @EnableEurekaClient to each of your services (Customer and Fraud) and register\nthis services to the registry. At this point you don't need to hard code the host, but simply use the **spring.application.name** of your client. \nSo in our case the endpoint would be something with the likes of \"http://FRAUD-APPLICATION/api/v1/fraud-check/{customerId}\".\n\nPlus remember, that RestTemplate being an HTTP type of request, it is synchronous, so the Customer microservice is blocked until a response isn't\nobtained from the Fraud microservice, which might impact the user experience, and keep stuck your entire microservice architecture.\n\nAt this point receiving the data from the consumer side is simple as creating a classic Controller. \n```java\npublic class FraudController {\n\n    private final FraudCheckService fraudCheckService;\n\n    @GetMapping(path = \"{customerId}\")\n    public FraudCheckResponse isFraudster(@PathVariable Integer customerId) {\n        boolean isFraudulentCustomer = fraudCheckService.isFraudulentCustomer(customerId);\n        log.info(\"fraud check request for customer {}\", customerId);\n        return new FraudCheckResponse(isFraudulentCustomer);\n    }\n}\n```\n\n## RabbitMQ\n\nFor scalable applications, it is better to use a message broker such as RabbitMQ. It is asynchronous meaning that a producer writes its data\nto a queue and there it stays until a consumer will read whenever its ready. In this way the Customer microservice doesn't remain stuck even\nif the Fraud microservice is down and cannot receive the data from the producer.\n\nTo implement RabbitMQ, we added it to the docker-compose file using it as a container\n```dockerfile\nrabbitmq:\n    image: rabbitmq:3.9.11-management-alpine\n    container_name: rabbitmq\n    ports:\n      - \"5672:5672\"\n      - \"15672:15672\"\n```\n\nWe the import the dependencies into each of our microservices:\n\n```xml\n\u003cdependecies\u003e\n  \u003cdependency\u003e\n    \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n    \u003cartifactId\u003espring-boot-starter-amqp\u003c/artifactId\u003e\n  \u003c/dependency\u003e\n  \u003cdependency\u003e\n    \u003cgroupId\u003eorg.springframework.amqp\u003c/groupId\u003e\n    \u003cartifactId\u003espring-rabbit-test\u003c/artifactId\u003e\n    \u003cscope\u003etest\u003c/scope\u003e\n  \u003c/dependency\u003e\n\u003c/dependecies\u003e\n```\n\nRabbitMQ works as follows:\n\n1. A Producer writes data into an *exchange*.\n2. The exchange writes data into a *queue*.\n3. The Consumer then, whenever it is able to, will read from the specific queue and will be able to use its data.\n\nProducer --\u003e Exchange --\u003e Queue \u003c-- Consumer.\n\nTo do so, we need a configuration file where we specify the Exchange, Queue and *RoutingKey* which binds the exchange with a particular queue, \nbecause it is possibile to have even multiple queue binded to a single exchange.\n\n```java\nimport org.springframework.amqp.core.*;\nimport org.springframework.amqp.rabbit.connection.ConnectionFactory;\nimport org.springframework.amqp.rabbit.core.RabbitTemplate;\nimport org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;\nimport org.springframework.amqp.support.converter.MessageConverter;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class RabbitMQConfig {\n\n    public static final String MESSAGE_QUEUE = \"message_queue\";\n    public static final String MESSAGE_EXCHANGE = \"message_exchange\";\n    public static final String ROUTING_KEY = \"message_routingKey\";\n\n    @Bean\n    public Queue queue() {\n        return new Queue(MESSAGE_QUEUE);\n    }\n\n    @Bean\n    public TopicExchange exchange() {\n        return new TopicExchange(MESSAGE_EXCHANGE);\n    }\n\n    @Bean\n    public Binding binding(Queue queue, TopicExchange topicExchange) {\n        return BindingBuilder.bind(queue).to(topicExchange).with(ROUTING_KEY);\n    }\n\n    @Bean\n    public MessageConverter messageConverter() {\n        return new Jackson2JsonMessageConverter();\n    }\n\n    @Bean\n    public AmqpTemplate template(ConnectionFactory connectionFactory) {\n        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);\n        rabbitTemplate.setMessageConverter(messageConverter());\n        return rabbitTemplate;\n\n    }\n}\n```\n\nAt this point we can inject the RabbitTemplate into our business logic layer as follows:\n\n```java\n\n@Service\n@AllArgsConstructor\n@Slf4j\npublic class CustomerService {\n    \n    private final CustomerRepository customerRepository;\n    private RabbitTemplate rabbitTemplate;\n    \n    public void registerCustomer(CustomerRegistrationRequest request) {\n        Customer customer = Customer.builder()\n                .firstName(request.firstName())\n                .lastName(request.lastName())\n                .email(request.email())\n                .build();\n        customerRepository.saveAndFlush(customer);\n        rabbitTemplate.convertAndSend(RabbitMQConfig.MESSAGE_EXCHANGE, RabbitMQConfig.ROUTING_KEY, customer);\n        log.info(\"Message published\");\n    }\n}\n```\n\nAs you can see, the pass to the *convertAndSend* method the Exchange name and the Routing Key, so that the data (the *customer* object),\nwill be then forwarded to the Queue called \"message_queue\" we defined in *RabbitMQConfig*.\n\nAt this point at the Consumer level, we will have to implement the RabbitMQConfig as well and copy-paste the Customer object (might as well use a different module and keep the common dtos in here).\n\nAt the service layer we then annotate the method with @RabbitListener and give it the queue name.\n\n```java\n@Slf4j\n@Service\n@AllArgsConstructor\npublic class FraudCheckService {\n\n    private final FraudCheckHistoryRepository fraudCheckHistoryRepository;\n    \n    @RabbitListener(queues = RabbitMQConfig.MESSAGE_QUEUE)\n    public void getCustomerFromQueue(Customer customer) {\n        log.info(\"Customer is {}\", customer.toString());\n        fraudCheckHistoryRepository.save(\n                FraudCheckHistory.builder()\n                        .customerId(customer.getId())\n                        .isFraudster(true)\n                        .createdAt(LocalDateTime.now())\n                        .build()\n        );\n    }\n}\n```\n\nAt this point this method will receive the Customer object that was sent from the Customer Service when we hit the \"saveCustomer\" endpoint.\n#### IMPORTANT: \nThe RabbitListener must be a void method, unless you want the Consumer to reply to the Producer. \n\n## Apache Kafka\nTo use Apache Kafka as the message broker, the bitname image was used, as it is the best maintained and\nthe best documented. \nTo create the Kafka server you need to get the Zookeeper and Kafka broker images as follows:\n```yaml\nservices:\n  zookeeper:\n    image: 'bitnami/zookeeper:latest'\n    container_name: zookeeper\n    ports:\n      - '2181:2181'\n    environment:\n      - ALLOW_ANONYMOUS_LOGIN=yes\n    networks:\n      - postgres\n  kafka:\n    image: 'bitnami/kafka:latest'\n    container_name: kafka\n    ports:\n      - '9092:9092'\n    environment:\n      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092\n      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://127.0.0.1:9092\n      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181\n      - ALLOW_PLAINTEXT_LISTENER=yes\n    networks:\n      - postgres\n```\nIt is important to have the same networks on which your microservices are running on. In our\ncase the network name is *postgres*.\n\n#### Producer Configuration\nThe producer is the microservice that sends the message to the broker, so in our case\nit is the Customer microservice.\nThere is some configuration needed to be done to use Kafka, like giving it the bootstrap servers as well as the\ntopic on which the Producer should write to:\n```java\n@Configuration\n@RequiredArgsConstructor\npublic class KafkaProducerConfig {\n\n    @Bean\n    public Map\u003cString, Object\u003e producerConfig() {\n        Map\u003cString, Object\u003e props = new HashMap\u003c\u003e();\n        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, \"localhost:9092\");\n        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);\n        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);\n        return props;\n    }\n\n    @Bean\n    public ProducerFactory\u003cString, String\u003e producerFactory() {\n        return new DefaultKafkaProducerFactory\u003c\u003e(producerConfig());\n    }\n\n    @Bean\n    public KafkaTemplate\u003cString, String\u003e kafkaTemplate(ProducerFactory\u003cString, String\u003e producerFactory) {\n        return new KafkaTemplate\u003c\u003e(producerFactory);\n    }\n\n    @Bean\n    public NewTopic dotjsonTopic() {\n        return TopicBuilder.name(\"dotjson\").build();\n    }\n}\n```\n\nNext, in the service layer, or wherever you want to be able to send the message to the queue, you implement the\nKafkaTemplate and use the *send* method, giving in the topic and the payload.\n```java\n@Service\n@AllArgsConstructor\n@Slf4j\npublic class CustomerService {\n\n    private final CustomerRepository customerRepository;\n    private final KafkaTemplate\u003cString, String\u003e kafkaTemplate;\n\n    public void registerCustomerKafka(CustomerRegistrationRequest request) {\n        Customer customer = Customer.builder()\n                .firstName(request.firstName())\n                .lastName(request.lastName())\n                .email(request.email())\n                .build();\n        customerRepository.saveAndFlush(customer);\n        kafkaTemplate.send(\"dotjson\", customer.getEmail());\n        log.info(\"Published to Kafka\");\n    }\n}\n```\n\nIf you want to be able to receive the messages from Kafka you need to setup some\nconfiguration on the Consumer as well. \n\n\nBasically implement the following configuration in the Fraud microservice, where as before\nwe give to Kafka the bootstrap server and the topic -\u003e *dotjson*.\n```java\n@Configuration\n@RequiredArgsConstructor\npublic class KafkaConsumerConfig {\n\n    @Bean\n    public Map\u003cString, Object\u003e consumerConfig() {\n        Map\u003cString, Object\u003e props = new HashMap\u003c\u003e();\n        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, \"localhost:9092\");\n        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringSerializer.class);\n        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringSerializer.class);\n        return props;\n    }\n\n    @Bean\n    public ConsumerFactory\u003cString, String\u003e consumerFactory() {\n        return new DefaultKafkaConsumerFactory\u003c\u003e(consumerConfig());\n    }\n\n    @Bean\n    public KafkaListenerContainerFactory\u003cConcurrentMessageListenerContainer\u003cString, String\u003e\u003e factory(ConsumerFactory\u003cString, String\u003e consumerFactory) {\n        ConcurrentKafkaListenerContainerFactory\u003cString, String\u003e factory = new ConcurrentKafkaListenerContainerFactory\u003c\u003e();\n        factory.setConsumerFactory(consumerFactory);\n        return factory;\n    }\n\n    @Bean\n    public NewTopic dotjsonTopic() {\n        return TopicBuilder.name(\"dotjson\").build();\n    }\n}\n```\n\nNext, in the Consumer we annotate the method with @KafkaListener, give the topic and \nthe groupId and as a parameter we pass the payload we want to receive from the Kafka queue.\n\n```java\n@Slf4j\n@Service\n@AllArgsConstructor\npublic class FraudCheckService {\n\n    private final FraudCheckHistoryRepository fraudCheckHistoryRepository;\n\n    @KafkaListener(topics = \"dotjson\", groupId = \"groupId\")\n    public void getCustomerFromKafka(String customerEmail) {\n        log.info(\"Received {}\", customerEmail);\n        fraudCheckHistoryRepository.save(\n                FraudCheckHistory.builder()\n                        .customerId(0)\n                        .isFraudster(true)\n                        .createdAt(LocalDateTime.now())\n                        .build()\n        );\n    }\n}\n```\n\n## Conclusion\nWe have here seen how to implement 3 types of microservice communication:\n1. ***REST Template***: it uses HTTP protocol, so it is a synchronous way of communicating. Might be good for small applications, but it blocks your entire architecture if the microservice cannot receive the payload.\n2. ***RabbitMQ***: It is super-easy to configure and implement and also easy to use. It uses the Message Queue Protocol so it is asynchronous, giving it a big advantage on the REST Template. \n3. ***Apache Kafka***: a bit difficult to implement and configure, it is asynchronous as well but it is able to persist the messagges in the queue forever, so it is a big advantage. Industry-standard so it might be useful to learn.\n\n#### Author\nJason Shuyinta","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjazzshu%2Fmicroservices-communication","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjazzshu%2Fmicroservices-communication","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjazzshu%2Fmicroservices-communication/lists"}