├── .gitignore ├── account-service ├── src │ ├── main │ │ ├── .gitignore │ │ ├── proto │ │ │ └── account.proto │ │ ├── resources │ │ │ └── application.yml │ │ └── java │ │ │ └── pl │ │ │ └── piomin │ │ │ └── services │ │ │ └── protobuf │ │ │ └── account │ │ │ ├── data │ │ │ └── AccountRepository.java │ │ │ ├── controller │ │ │ └── AccountController.java │ │ │ └── Application.java │ └── test │ │ └── java │ │ └── pl │ │ └── piomin │ │ └── services │ │ └── protobuf │ │ └── account │ │ └── AccountApplicationTest.java ├── .gitignore └── pom.xml ├── customer-service ├── src │ ├── main │ │ ├── .gitignore │ │ ├── java │ │ │ └── pl │ │ │ │ └── piomin │ │ │ │ └── services │ │ │ │ └── protobuf │ │ │ │ └── customer │ │ │ │ ├── config │ │ │ │ ├── ServiceConfig.java │ │ │ │ └── LoadBalancerConfigurationProperties.java │ │ │ │ ├── contract │ │ │ │ └── AccountClient.java │ │ │ │ ├── data │ │ │ │ └── CustomerRepository.java │ │ │ │ ├── loadbalancer │ │ │ │ ├── StaticServiceInstance.java │ │ │ │ └── StaticServiceInstanceListSupplier.java │ │ │ │ ├── Application.java │ │ │ │ └── controller │ │ │ │ └── CustomerController.java │ │ ├── resources │ │ │ └── application.yml │ │ └── proto │ │ │ └── customer.proto │ └── test │ │ └── java │ │ └── pl │ │ └── piomin │ │ └── services │ │ └── protobuf │ │ └── customer │ │ └── CustomerApplicationTest.java ├── .gitignore └── pom.xml ├── account-service-grpc ├── src │ ├── main │ │ ├── resources │ │ │ └── application.yml │ │ ├── proto │ │ │ └── account.proto │ │ └── java │ │ │ └── pl │ │ │ └── piomin │ │ │ └── services │ │ │ └── grpc │ │ │ └── account │ │ │ ├── repository │ │ │ └── AccountRepository.java │ │ │ ├── AccountApplication.java │ │ │ └── service │ │ │ └── AccountsService.java │ └── test │ │ └── java │ │ └── pl │ │ └── piomin │ │ └── services │ │ └── grpc │ │ └── account │ │ └── AccountServicesTests.java └── pom.xml ├── renovate.json ├── customer-service-grpc ├── src │ ├── main │ │ ├── resources │ │ │ └── application.yml │ │ ├── java │ │ │ └── pl │ │ │ │ └── piomin │ │ │ │ └── services │ │ │ │ └── grpc │ │ │ │ └── customer │ │ │ │ ├── client │ │ │ │ └── AccountClient.java │ │ │ │ ├── repository │ │ │ │ └── CustomerRepository.java │ │ │ │ ├── CustomerApplication.java │ │ │ │ └── service │ │ │ │ └── CustomersService.java │ │ └── proto │ │ │ └── customer.proto │ └── test │ │ └── java │ │ └── pl │ │ └── piomin │ │ └── services │ │ └── grpc │ │ └── customer │ │ └── CustomerServicesTests.java └── pom.xml ├── discovery-service ├── src │ └── main │ │ ├── resources │ │ └── application.yml │ │ └── java │ │ └── pl │ │ └── piomin │ │ └── services │ │ └── discovery │ │ └── DiscoveryService.java └── pom.xml ├── .circleci └── config.yml ├── pom.xml └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | /.settings/ 3 | -------------------------------------------------------------------------------- /account-service/src/main/.gitignore: -------------------------------------------------------------------------------- 1 | /generated/ 2 | -------------------------------------------------------------------------------- /customer-service/src/main/.gitignore: -------------------------------------------------------------------------------- 1 | /generated/ 2 | -------------------------------------------------------------------------------- /account-service/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | /.classpath 4 | /.project 5 | -------------------------------------------------------------------------------- /customer-service/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | /.classpath 4 | /.project 5 | -------------------------------------------------------------------------------- /account-service-grpc/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring.application.name: account-service-grpc 2 | spring.output.ansi.enabled: always 3 | 4 | management.endpoints.web.exposure.include: metrics 5 | management.endpoint.metrics.enabled: true -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base",":dependencyDashboard" 5 | ], 6 | "packageRules": [ 7 | { 8 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 9 | "automerge": true 10 | } 11 | ], 12 | "prCreation": "not-pending" 13 | } -------------------------------------------------------------------------------- /customer-service-grpc/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring.application.name: customer-service-grpc 2 | spring.output.ansi.enabled: always 3 | 4 | management.endpoints.web.exposure.include: metrics 5 | management.endpoint.metrics.enabled: true 6 | 7 | server.port: 8081 8 | spring.grpc.server.port: 9091 9 | 10 | spring.grpc.client.channels.local.address: localhost:9090 -------------------------------------------------------------------------------- /account-service/src/main/proto/account.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package model; 4 | 5 | option java_package = "pl.piomin.services.protobuf.account.model"; 6 | option java_outer_classname = "AccountProto"; 7 | 8 | message Accounts { 9 | repeated Account account = 1; 10 | } 11 | 12 | message Account { 13 | 14 | int32 id = 1; 15 | string number = 2; 16 | int32 customer_id = 3; 17 | 18 | } -------------------------------------------------------------------------------- /discovery-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8761 3 | 4 | eureka: 5 | instance: 6 | hostname: localhost 7 | client: 8 | registerWithEureka: false 9 | fetchRegistry: false 10 | serviceUrl: 11 | defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ 12 | 13 | spring: 14 | application: 15 | name: discovery-service 16 | output: 17 | ansi: 18 | enabled: always -------------------------------------------------------------------------------- /account-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: ${PORT:2222} 3 | 4 | spring: 5 | application: 6 | name: account-service 7 | 8 | #eureka: 9 | # client: 10 | # serviceUrl: 11 | # defaultZone: ${DISCOVERY_URL:http://localhost:8761}/eureka/ 12 | # instance: 13 | # leaseRenewalIntervalInSeconds: 1 14 | # leaseExpirationDurationInSeconds: 2 15 | 16 | #ribbon: 17 | # eureka: 18 | # enabled: true -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | build: 5 | docker: 6 | - image: 'cimg/openjdk:25.0' 7 | steps: 8 | - checkout 9 | - run: 10 | name: Analyze on SonarCloud 11 | command: mvn verify sonar:sonar 12 | 13 | executors: 14 | jdk: 15 | docker: 16 | - image: 'cimg/openjdk:25.0' 17 | 18 | orbs: 19 | maven: circleci/maven@2.1.1 20 | 21 | workflows: 22 | maven_test: 23 | jobs: 24 | - maven/test: 25 | executor: jdk 26 | - build: 27 | context: SonarCloud -------------------------------------------------------------------------------- /discovery-service/src/main/java/pl/piomin/services/discovery/DiscoveryService.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.discovery; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @SpringBootApplication 8 | @EnableEurekaServer 9 | public class DiscoveryService { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(DiscoveryService.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /customer-service/src/main/java/pl/piomin/services/protobuf/customer/config/ServiceConfig.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.protobuf.customer.config; 2 | 3 | public class ServiceConfig { 4 | 5 | private String name; 6 | private String servers; 7 | 8 | public String getName() { 9 | return name; 10 | } 11 | 12 | public void setName(String name) { 13 | this.name = name; 14 | } 15 | 16 | public String getServers() { 17 | return servers; 18 | } 19 | 20 | public void setServers(String servers) { 21 | this.servers = servers; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /customer-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: ${PORT:3333} 3 | 4 | spring: 5 | application: 6 | name: customer-service 7 | cloud: 8 | loadbalancer: 9 | ribbon: 10 | enabled: false 11 | instances: 12 | - name: account-service 13 | servers: localhost:2222 14 | 15 | #eureka: 16 | # client: 17 | # serviceUrl: 18 | # defaultZone: ${DISCOVERY_URL:http://localhost:8761}/eureka/ 19 | # instance: 20 | # leaseRenewalIntervalInSeconds: 1 21 | # leaseExpirationDurationInSeconds: 2 22 | 23 | ribbon: 24 | listOfServers: localhost:2222 25 | # eureka: 26 | # enabled: true -------------------------------------------------------------------------------- /customer-service/src/main/proto/customer.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package model; 4 | 5 | option java_package = "pl.piomin.services.protobuf.customer.model"; 6 | option java_outer_classname = "CustomerProto"; 7 | 8 | message Accounts { 9 | repeated Account account = 1; 10 | } 11 | 12 | message Account { 13 | 14 | int32 id = 1; 15 | string number = 2; 16 | int32 customer_id = 3; 17 | 18 | } 19 | 20 | message Customers { 21 | repeated Customer customers = 1; 22 | } 23 | 24 | message Customer { 25 | 26 | int32 id = 1; 27 | string pesel = 2; 28 | string name = 3; 29 | CustomerType type = 4; 30 | repeated Account accounts = 5; 31 | 32 | enum CustomerType { 33 | INDIVIDUAL = 0; 34 | COMPANY = 1; 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /account-service-grpc/src/main/proto/account.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package model; 4 | 5 | option java_package = "pl.piomin.services.grpc.account.model"; 6 | option java_outer_classname = "AccountProto"; 7 | 8 | import "google/protobuf/empty.proto"; 9 | import "google/protobuf/wrappers.proto"; 10 | 11 | service AccountsService { 12 | rpc FindByNumber(google.protobuf.StringValue) returns (Account) {} 13 | rpc FindByCustomer(google.protobuf.Int32Value) returns (Accounts) {} 14 | rpc FindAll(google.protobuf.Empty) returns (Accounts) {} 15 | rpc AddAccount(Account) returns (Account) {} 16 | } 17 | 18 | message Accounts { 19 | repeated Account account = 1; 20 | } 21 | 22 | message Account { 23 | int32 id = 1; 24 | string number = 2; 25 | int32 customer_id = 3; 26 | } -------------------------------------------------------------------------------- /customer-service/src/main/java/pl/piomin/services/protobuf/customer/contract/AccountClient.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.protobuf.customer.contract; 2 | 3 | import org.springframework.cloud.openfeign.FeignClient; 4 | import org.springframework.web.bind.annotation.PathVariable; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestMethod; 7 | 8 | import pl.piomin.services.protobuf.customer.model.CustomerProto.Accounts; 9 | 10 | @FeignClient(value = "account-service") 11 | public interface AccountClient { 12 | 13 | @RequestMapping(method = RequestMethod.GET, value = "/accounts/customer/{customerId}") 14 | Accounts getAccounts(@PathVariable("customerId") Integer customerId); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /customer-service/src/main/java/pl/piomin/services/protobuf/customer/config/LoadBalancerConfigurationProperties.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.protobuf.customer.config; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | @Configuration 10 | @ConfigurationProperties("spring.cloud.loadbalancer") 11 | public class LoadBalancerConfigurationProperties { 12 | 13 | List instances = new ArrayList<>(); 14 | 15 | public List getInstances() { 16 | return instances; 17 | } 18 | 19 | public void setInstances(List instances) { 20 | this.instances = instances; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /customer-service/src/main/java/pl/piomin/services/protobuf/customer/data/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.protobuf.customer.data; 2 | 3 | import java.util.List; 4 | 5 | import pl.piomin.services.protobuf.customer.model.CustomerProto.Customer; 6 | 7 | public class CustomerRepository { 8 | 9 | private List customers; 10 | 11 | public CustomerRepository(List customers) { 12 | this.customers = customers; 13 | } 14 | 15 | public Customer findById(int id) { 16 | return customers.stream().filter(it -> it.getId() == id).findFirst().orElseThrow(); 17 | } 18 | 19 | public Customer findByPesel(String pesel) { 20 | return customers.stream().filter(it -> it.getPesel().equals(pesel)).findFirst().orElseThrow(); 21 | } 22 | 23 | public List findAll() { 24 | return customers; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /account-service/src/main/java/pl/piomin/services/protobuf/account/data/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.protobuf.account.data; 2 | 3 | import java.util.List; 4 | 5 | import pl.piomin.services.protobuf.account.model.AccountProto.Account; 6 | 7 | public class AccountRepository { 8 | 9 | List accounts; 10 | 11 | public AccountRepository(List accounts) { 12 | this.accounts = accounts; 13 | } 14 | 15 | public List findAll() { 16 | return accounts; 17 | } 18 | 19 | public List findByCustomer(int customerId) { 20 | return accounts.stream().filter(it -> it.getCustomerId() == customerId).toList(); 21 | } 22 | 23 | public Account findByNumber(String number) { 24 | return accounts.stream() 25 | .filter(it -> it.getNumber().equals(number)) 26 | .findFirst() 27 | .orElseThrow(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /customer-service/src/main/java/pl/piomin/services/protobuf/customer/loadbalancer/StaticServiceInstance.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.protobuf.customer.loadbalancer; 2 | 3 | import org.springframework.cloud.client.ServiceInstance; 4 | 5 | import java.net.URI; 6 | import java.util.Map; 7 | 8 | public class StaticServiceInstance implements ServiceInstance { 9 | 10 | private String serviceName; 11 | private String address; 12 | 13 | public StaticServiceInstance(String serviceName, String address) { 14 | this.serviceName = serviceName; 15 | this.address = address; 16 | } 17 | 18 | @Override 19 | public String getServiceId() { 20 | return serviceName; 21 | } 22 | 23 | @Override 24 | public String getHost() { 25 | return address.split(":", 2)[0]; 26 | } 27 | 28 | @Override 29 | public int getPort() { 30 | return Integer.parseInt(address.split(":", 2)[1]); 31 | } 32 | 33 | @Override 34 | public boolean isSecure() { 35 | return false; 36 | } 37 | 38 | @Override 39 | public URI getUri() { 40 | return null; 41 | } 42 | 43 | @Override 44 | public Map getMetadata() { 45 | return Map.of(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /customer-service-grpc/src/main/java/pl/piomin/services/grpc/customer/client/AccountClient.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.grpc.customer.client; 2 | 3 | import com.google.protobuf.Int32Value; 4 | import io.grpc.StatusRuntimeException; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.stereotype.Service; 8 | import pl.piomin.services.grpc.customer.model.AccountsServiceGrpc; 9 | import pl.piomin.services.grpc.customer.model.CustomerProto; 10 | 11 | @Service 12 | public class AccountClient { 13 | 14 | private static final Logger LOG = LoggerFactory.getLogger(AccountClient.class); 15 | AccountsServiceGrpc.AccountsServiceBlockingStub accountsClient; 16 | 17 | public AccountClient(AccountsServiceGrpc.AccountsServiceBlockingStub accountsClient) { 18 | this.accountsClient = accountsClient; 19 | } 20 | 21 | public CustomerProto.Accounts getAccountsByCustomerId(int customerId) { 22 | try { 23 | return accountsClient.findByCustomer(Int32Value.newBuilder() 24 | .setValue(customerId) 25 | .build()); 26 | } catch (final StatusRuntimeException e) { 27 | LOG.error("Error in communication", e); 28 | return null; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /customer-service-grpc/src/main/proto/customer.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package model; 4 | 5 | option java_package = "pl.piomin.services.grpc.customer.model"; 6 | option java_outer_classname = "CustomerProto"; 7 | 8 | import "google/protobuf/empty.proto"; 9 | import "google/protobuf/wrappers.proto"; 10 | 11 | service CustomersService { 12 | rpc FindByPesel(google.protobuf.StringValue) returns (Customer) {} 13 | rpc FindById(google.protobuf.Int32Value) returns (Customer) {} 14 | rpc FindAll(google.protobuf.Empty) returns (Customers) {} 15 | rpc AddCustomer(Customer) returns (Customer) {} 16 | } 17 | 18 | service AccountsService { 19 | rpc FindByNumber(google.protobuf.StringValue) returns (Account) {} 20 | rpc FindByCustomer(google.protobuf.Int32Value) returns (Accounts) {} 21 | rpc FindAll(google.protobuf.Empty) returns (Accounts) {} 22 | rpc AddAccount(Account) returns (Account) {} 23 | } 24 | 25 | message Accounts { 26 | repeated Account account = 1; 27 | } 28 | 29 | message Account { 30 | int32 id = 1; 31 | string number = 2; 32 | int32 customer_id = 3; 33 | } 34 | 35 | message Customer { 36 | int32 id = 1; 37 | string pesel = 2; 38 | string name = 3; 39 | CustomerType type = 4; 40 | repeated Account accounts = 5; 41 | enum CustomerType { 42 | INDIVIDUAL = 0; 43 | COMPANY = 1; 44 | } 45 | } 46 | 47 | message Customers { 48 | repeated Customer customers = 1; 49 | } -------------------------------------------------------------------------------- /account-service-grpc/src/main/java/pl/piomin/services/grpc/account/repository/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.grpc.account.repository; 2 | 3 | 4 | import pl.piomin.services.grpc.account.model.AccountProto; 5 | 6 | import java.util.List; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | public class AccountRepository { 10 | 11 | List accounts; 12 | AtomicInteger id; 13 | 14 | public AccountRepository(List accounts) { 15 | this.accounts = accounts; 16 | this.id = new AtomicInteger(); 17 | this.id.set(accounts.size()); 18 | } 19 | 20 | public List findAll() { 21 | return accounts; 22 | } 23 | 24 | public List findByCustomer(int customerId) { 25 | return accounts.stream().filter(it -> it.getCustomerId() == customerId) 26 | .toList(); 27 | } 28 | 29 | public AccountProto.Account findByNumber(String number) { 30 | return accounts.stream() 31 | .filter(it -> it.getNumber().equals(number)) 32 | .findFirst() 33 | .orElseThrow(); 34 | } 35 | 36 | public AccountProto.Account add(int customerId, String number) { 37 | return AccountProto.Account.newBuilder() 38 | .setId(id.incrementAndGet()) 39 | .setCustomerId(customerId) 40 | .setNumber(number) 41 | .build(); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /customer-service-grpc/src/main/java/pl/piomin/services/grpc/customer/repository/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.grpc.customer.repository; 2 | 3 | 4 | import pl.piomin.services.grpc.customer.model.CustomerProto; 5 | 6 | import java.util.List; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | public class CustomerRepository { 10 | 11 | private List customers; 12 | AtomicInteger id; 13 | 14 | public CustomerRepository(List customers) { 15 | this.customers = customers; 16 | this.id = new AtomicInteger(); 17 | this.id.set(customers.size()); 18 | } 19 | 20 | public CustomerProto.Customer findById(int id) { 21 | return customers.stream().filter(it -> it.getId() == id).findFirst().orElseThrow(); 22 | } 23 | 24 | public CustomerProto.Customer findByPesel(String pesel) { 25 | return customers.stream().filter(it -> it.getPesel().equals(pesel)).findFirst().orElseThrow(); 26 | } 27 | 28 | public List findAll() { 29 | return customers; 30 | } 31 | 32 | public CustomerProto.Customer add(CustomerProto.Customer.CustomerType type, String name, String pesel) { 33 | CustomerProto.Customer c = CustomerProto.Customer.newBuilder() 34 | .setId(id.incrementAndGet()) 35 | .setType(type) 36 | .setName(name) 37 | .setPesel(pesel) 38 | .build(); 39 | return c; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /discovery-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 4.0.1 11 | 12 | 13 | 14 | pl.piomin 15 | discovery-service 16 | 1.0-SNAPSHOT 17 | 18 | 19 | 2025.1.0 20 | 21 | 22 | 23 | 24 | org.springframework.cloud 25 | spring-cloud-starter-netflix-eureka-server 26 | 27 | 28 | 29 | 30 | 31 | 32 | org.springframework.cloud 33 | spring-cloud-dependencies 34 | ${spring-cloud.version} 35 | pom 36 | import 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /customer-service/src/main/java/pl/piomin/services/protobuf/customer/loadbalancer/StaticServiceInstanceListSupplier.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.protobuf.customer.loadbalancer; 2 | 3 | import org.springframework.cloud.client.ServiceInstance; 4 | import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; 5 | import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; 6 | import org.springframework.core.env.Environment; 7 | import pl.piomin.services.protobuf.customer.config.LoadBalancerConfigurationProperties; 8 | import pl.piomin.services.protobuf.customer.config.ServiceConfig; 9 | import reactor.core.publisher.Flux; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | public class StaticServiceInstanceListSupplier implements ServiceInstanceListSupplier { 16 | 17 | private LoadBalancerConfigurationProperties properties; 18 | private Environment environment; 19 | 20 | @Override 21 | public String getServiceId() { 22 | return environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); 23 | } 24 | 25 | @Override 26 | public Flux> get() { 27 | ServiceConfig config = properties.getInstances().stream() 28 | .filter(it -> it.getName().equals(getServiceId())) 29 | .findAny() 30 | .orElseThrow(); 31 | 32 | List instances = Arrays.stream(config.getServers().split(",", 0)) 33 | .map( it -> new StaticServiceInstance(getServiceId(), it)) 34 | .collect(Collectors.toList()); 35 | 36 | return Flux.just(instances); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /account-service-grpc/src/main/java/pl/piomin/services/grpc/account/AccountApplication.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.grpc.account; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Bean; 6 | import pl.piomin.services.grpc.account.model.AccountProto; 7 | import pl.piomin.services.grpc.account.repository.AccountRepository; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | @SpringBootApplication 13 | public class AccountApplication { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(AccountApplication.class, args); 17 | } 18 | 19 | @Bean 20 | AccountRepository repository() { 21 | List accounts = new ArrayList<>(); 22 | accounts.add(AccountProto.Account.newBuilder().setId(1).setCustomerId(1).setNumber("111111").build()); 23 | accounts.add(AccountProto.Account.newBuilder().setId(2).setCustomerId(2).setNumber("222222").build()); 24 | accounts.add(AccountProto.Account.newBuilder().setId(3).setCustomerId(3).setNumber("333333").build()); 25 | accounts.add(AccountProto.Account.newBuilder().setId(4).setCustomerId(4).setNumber("444444").build()); 26 | accounts.add(AccountProto.Account.newBuilder().setId(5).setCustomerId(1).setNumber("555555").build()); 27 | accounts.add(AccountProto.Account.newBuilder().setId(6).setCustomerId(2).setNumber("666666").build()); 28 | accounts.add(AccountProto.Account.newBuilder().setId(7).setCustomerId(2).setNumber("777777").build()); 29 | return new AccountRepository(accounts); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /customer-service-grpc/src/test/java/pl/piomin/services/grpc/customer/CustomerServicesTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.grpc.customer; 2 | 3 | import com.google.protobuf.Empty; 4 | import com.google.protobuf.StringValue; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.grpc.test.autoconfigure.AutoConfigureInProcessTransport; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.boot.test.context.TestConfiguration; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.grpc.client.GrpcChannelFactory; 12 | import pl.piomin.services.grpc.customer.model.CustomerProto; 13 | import pl.piomin.services.grpc.customer.model.CustomersServiceGrpc; 14 | 15 | import static org.junit.jupiter.api.Assertions.*; 16 | 17 | @SpringBootTest 18 | @AutoConfigureInProcessTransport 19 | public class CustomerServicesTests { 20 | 21 | @Autowired 22 | CustomersServiceGrpc.CustomersServiceBlockingStub service; 23 | 24 | @Test 25 | void shouldFindAll() { 26 | CustomerProto.Customers c = service.findAll(Empty.newBuilder().build()); 27 | assertNotNull(c); 28 | assertFalse(c.getCustomersList().isEmpty()); 29 | } 30 | 31 | @Test 32 | void shouldFindByPesel() { 33 | CustomerProto.Customer c = service.findByPesel(StringValue.newBuilder().setValue("12345").build()); 34 | assertNotNull(c); 35 | assertNotEquals(0, c.getId()); 36 | } 37 | 38 | @TestConfiguration 39 | static class Config { 40 | 41 | @Bean 42 | CustomersServiceGrpc.CustomersServiceBlockingStub stub(GrpcChannelFactory channels) { 43 | return CustomersServiceGrpc.newBlockingStub(channels.createChannel("local")); 44 | } 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /account-service/src/main/java/pl/piomin/services/protobuf/account/controller/AccountController.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.protobuf.account.controller; 2 | 3 | import java.util.logging.Logger; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import pl.piomin.services.protobuf.account.data.AccountRepository; 11 | import pl.piomin.services.protobuf.account.model.AccountProto.Account; 12 | import pl.piomin.services.protobuf.account.model.AccountProto.Accounts; 13 | 14 | @RestController 15 | public class AccountController { 16 | 17 | @Autowired 18 | AccountRepository repository; 19 | 20 | protected Logger logger = Logger.getLogger(AccountController.class.getName()); 21 | 22 | @RequestMapping(value = "/accounts/{number}", produces = "application/x-protobuf") 23 | public Account findByNumber(@PathVariable("number") String number) { 24 | logger.info(String.format("Account.findByNumber(%s)", number)); 25 | return repository.findByNumber(number); 26 | } 27 | 28 | @RequestMapping(value = "/accounts/customer/{customer}", produces = "application/x-protobuf") 29 | public Accounts findByCustomer(@PathVariable("customer") Integer customerId) { 30 | logger.info(String.format("Account.findByCustomer(%s)", customerId)); 31 | return Accounts.newBuilder().addAllAccount(repository.findByCustomer(customerId)).build(); 32 | } 33 | 34 | @RequestMapping(value = "/accounts", produces = "application/x-protobuf") 35 | public Accounts findAll() { 36 | logger.info("Account.findAll()"); 37 | return Accounts.newBuilder().addAllAccount(repository.findAll()).build(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /customer-service/src/test/java/pl/piomin/services/protobuf/customer/CustomerApplicationTest.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.protobuf.customer; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 7 | import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter; 8 | import org.springframework.test.web.servlet.client.RestTestClient; 9 | import org.springframework.web.context.WebApplicationContext; 10 | import pl.piomin.services.protobuf.customer.model.CustomerProto.Customer; 11 | import pl.piomin.services.protobuf.customer.model.CustomerProto.Customers; 12 | 13 | @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 14 | public class CustomerApplicationTest { 15 | 16 | RestTestClient template; 17 | 18 | @BeforeEach 19 | void setUp(WebApplicationContext context) { 20 | template = RestTestClient.bindToApplicationContext(context) 21 | .baseUrl("/customers") 22 | .configureMessageConverters(clientBuilder -> clientBuilder.addCustomConverter(new ProtobufHttpMessageConverter())) 23 | .build(); 24 | } 25 | 26 | // @Test 27 | public void testFindById() { 28 | template.get().uri("/{id}", 1) 29 | .exchange() 30 | .expectStatus().is2xxSuccessful() 31 | .expectBody(Customer.class); 32 | } 33 | 34 | @Test 35 | public void testFindByPesel() { 36 | template.get().uri("/pesel/{pesel}", "12346") 37 | .exchange() 38 | .expectStatus().is2xxSuccessful() 39 | .expectBody(Customer.class); 40 | } 41 | 42 | @Test 43 | public void testFindAll() { 44 | template.get().exchange() 45 | .expectStatus().is2xxSuccessful() 46 | .expectBody(Customers.class); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | pl.piomin 5 | sample-microservices-protobuf 6 | 1.1-SNAPSHOT 7 | pom 8 | 9 | 10 | 21 11 | piomin_sample-microservices-protobuf 12 | piomin 13 | https://sonarcloud.io 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter-parent 19 | 4.0.1 20 | 21 | 22 | 23 | 24 | 25 | org.springframework.cloud 26 | spring-cloud-dependencies 27 | 2025.1.0 28 | pom 29 | import 30 | 31 | 32 | org.springframework.grpc 33 | spring-grpc-dependencies 34 | 1.0.0 35 | pom 36 | import 37 | 38 | 39 | 40 | 41 | 42 | account-service 43 | customer-service 44 | account-service-grpc 45 | customer-service-grpc 46 | discovery-service 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /customer-service-grpc/src/main/java/pl/piomin/services/grpc/customer/CustomerApplication.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.grpc.customer; 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.grpc.client.GrpcChannelFactory; 7 | import pl.piomin.services.grpc.customer.model.AccountsServiceGrpc; 8 | import pl.piomin.services.grpc.customer.model.CustomerProto; 9 | import pl.piomin.services.grpc.customer.repository.CustomerRepository; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | @SpringBootApplication 15 | public class CustomerApplication { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(CustomerApplication.class, args); 19 | } 20 | 21 | @Bean 22 | CustomerRepository repository() { 23 | List customers = new ArrayList<>(); 24 | customers.add(CustomerProto.Customer.newBuilder().setId(1).setPesel("12345").setName("Adam Kowalski") 25 | .setType(CustomerProto.Customer.CustomerType.INDIVIDUAL).build()); 26 | customers.add(CustomerProto.Customer.newBuilder().setId(2).setPesel("12346").setName("Anna Malinowska") 27 | .setType(CustomerProto.Customer.CustomerType.INDIVIDUAL).build()); 28 | customers.add(CustomerProto.Customer.newBuilder().setId(3).setPesel("12347").setName("Paweł Michalski") 29 | .setType(CustomerProto.Customer.CustomerType.INDIVIDUAL).build()); 30 | customers.add(CustomerProto.Customer.newBuilder().setId(4).setPesel("12348").setName("Karolina Lewandowska") 31 | .setType(CustomerProto.Customer.CustomerType.INDIVIDUAL).build()); 32 | return new CustomerRepository(customers); 33 | } 34 | 35 | @Bean 36 | AccountsServiceGrpc.AccountsServiceBlockingStub accountsClient(GrpcChannelFactory channels) { 37 | return AccountsServiceGrpc.newBlockingStub(channels.createChannel("local")); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /account-service/src/main/java/pl/piomin/services/protobuf/account/Application.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.protobuf.account; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Primary; 11 | import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter; 12 | import org.springframework.web.client.RestTemplate; 13 | 14 | import pl.piomin.services.protobuf.account.data.AccountRepository; 15 | import pl.piomin.services.protobuf.account.model.AccountProto.Account; 16 | 17 | @SpringBootApplication 18 | public class Application { 19 | 20 | public static void main(String[] args) { 21 | SpringApplication.run(Application.class, args); 22 | } 23 | 24 | @Bean 25 | @Primary 26 | ProtobufHttpMessageConverter protobufHttpMessageConverter() { 27 | return new ProtobufHttpMessageConverter(); 28 | } 29 | 30 | @Bean 31 | RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) { 32 | return new RestTemplate(Arrays.asList(hmc)); 33 | } 34 | 35 | @Bean 36 | AccountRepository repository() { 37 | List accounts = new ArrayList<>(); 38 | accounts.add(Account.newBuilder().setId(1).setCustomerId(1).setNumber("111111").build()); 39 | accounts.add(Account.newBuilder().setId(2).setCustomerId(2).setNumber("222222").build()); 40 | accounts.add(Account.newBuilder().setId(3).setCustomerId(3).setNumber("333333").build()); 41 | accounts.add(Account.newBuilder().setId(4).setCustomerId(4).setNumber("444444").build()); 42 | accounts.add(Account.newBuilder().setId(5).setCustomerId(1).setNumber("555555").build()); 43 | accounts.add(Account.newBuilder().setId(6).setCustomerId(2).setNumber("666666").build()); 44 | accounts.add(Account.newBuilder().setId(7).setCustomerId(2).setNumber("777777").build()); 45 | return new AccountRepository(accounts); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /customer-service/src/main/java/pl/piomin/services/protobuf/customer/Application.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.protobuf.customer; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.openfeign.EnableFeignClients; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter; 8 | import org.springframework.web.client.RestTemplate; 9 | import pl.piomin.services.protobuf.customer.data.CustomerRepository; 10 | import pl.piomin.services.protobuf.customer.model.CustomerProto.Customer; 11 | import pl.piomin.services.protobuf.customer.model.CustomerProto.Customer.CustomerType; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | @SpringBootApplication 18 | @EnableFeignClients 19 | public class Application { 20 | 21 | public static void main(String[] args) { 22 | SpringApplication.run(Application.class, args); 23 | } 24 | 25 | @Bean 26 | ProtobufHttpMessageConverter protobufHttpMessageConverter() { 27 | return new ProtobufHttpMessageConverter(); 28 | } 29 | 30 | @Bean 31 | RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) { 32 | return new RestTemplate(Arrays.asList(hmc)); 33 | } 34 | 35 | @Bean 36 | CustomerRepository repository() { 37 | List customers = new ArrayList<>(); 38 | customers.add(Customer.newBuilder().setId(1).setPesel("12345").setName("Adam Kowalski") 39 | .setType(CustomerType.INDIVIDUAL).build()); 40 | customers.add(Customer.newBuilder().setId(2).setPesel("12346").setName("Anna Malinowska") 41 | .setType(CustomerType.INDIVIDUAL).build()); 42 | customers.add(Customer.newBuilder().setId(3).setPesel("12347").setName("Paweł Michalski") 43 | .setType(CustomerType.INDIVIDUAL).build()); 44 | customers.add(Customer.newBuilder().setId(4).setPesel("12348").setName("Karolina Lewandowska") 45 | .setType(CustomerType.INDIVIDUAL).build()); 46 | return new CustomerRepository(customers); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /account-service-grpc/src/main/java/pl/piomin/services/grpc/account/service/AccountsService.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.grpc.account.service; 2 | 3 | import com.google.protobuf.Empty; 4 | import com.google.protobuf.Int32Value; 5 | import com.google.protobuf.StringValue; 6 | import io.grpc.stub.StreamObserver; 7 | import org.springframework.grpc.server.service.GrpcService; 8 | import pl.piomin.services.grpc.account.model.AccountProto; 9 | import pl.piomin.services.grpc.account.model.AccountsServiceGrpc; 10 | import pl.piomin.services.grpc.account.repository.AccountRepository; 11 | 12 | import java.util.List; 13 | 14 | @GrpcService 15 | public class AccountsService extends AccountsServiceGrpc.AccountsServiceImplBase { 16 | 17 | AccountRepository repository; 18 | 19 | public AccountsService(AccountRepository repository) { 20 | this.repository = repository; 21 | } 22 | 23 | @Override 24 | public void findByNumber(StringValue request, StreamObserver responseObserver) { 25 | AccountProto.Account a = repository.findByNumber(request.getValue()); 26 | responseObserver.onNext(a); 27 | responseObserver.onCompleted(); 28 | } 29 | 30 | @Override 31 | public void findByCustomer(Int32Value request, StreamObserver responseObserver) { 32 | List accounts = repository.findByCustomer(request.getValue()); 33 | AccountProto.Accounts a = AccountProto.Accounts.newBuilder().addAllAccount(accounts).build(); 34 | responseObserver.onNext(a); 35 | responseObserver.onCompleted(); 36 | } 37 | 38 | @Override 39 | public void findAll(Empty request, StreamObserver responseObserver) { 40 | List accounts = repository.findAll(); 41 | AccountProto.Accounts a = AccountProto.Accounts.newBuilder().addAllAccount(accounts).build(); 42 | responseObserver.onNext(a); 43 | responseObserver.onCompleted(); 44 | } 45 | 46 | @Override 47 | public void addAccount(AccountProto.Account request, StreamObserver responseObserver) { 48 | AccountProto.Account a = repository.add(request.getCustomerId(), request.getNumber()); 49 | responseObserver.onNext(a); 50 | responseObserver.onCompleted(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /customer-service/src/main/java/pl/piomin/services/protobuf/customer/controller/CustomerController.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.protobuf.customer.controller; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import pl.piomin.services.protobuf.customer.contract.AccountClient; 11 | import pl.piomin.services.protobuf.customer.data.CustomerRepository; 12 | import pl.piomin.services.protobuf.customer.model.CustomerProto.Accounts; 13 | import pl.piomin.services.protobuf.customer.model.CustomerProto.Customer; 14 | import pl.piomin.services.protobuf.customer.model.CustomerProto.Customers; 15 | 16 | @RestController 17 | public class CustomerController { 18 | 19 | protected Logger logger = LoggerFactory.getLogger(CustomerController.class.getName()); 20 | 21 | @Autowired 22 | CustomerRepository repository; 23 | @Autowired 24 | AccountClient accountClient; 25 | 26 | public CustomerController(CustomerRepository repository, AccountClient accountClient) { 27 | this.repository = repository; 28 | this.accountClient = accountClient; 29 | } 30 | 31 | @RequestMapping(value = "/customers/pesel/{pesel}", produces = "application/x-protobuf") 32 | public Customer findByPesel(@PathVariable String pesel) { 33 | logger.info("Customer.findByPesel({})", pesel); 34 | return repository.findByPesel(pesel); 35 | } 36 | 37 | @RequestMapping(value = "/customers", produces = "application/x-protobuf") 38 | public Customers findAll() { 39 | logger.info("Customer.findAll()"); 40 | return Customers.newBuilder().addAllCustomers(repository.findAll()).build(); 41 | } 42 | 43 | @RequestMapping(value = "/customers/{id}", produces = "application/x-protobuf") 44 | public Customer findById(@PathVariable Integer id) { 45 | logger.info("Customer.findById({})", id); 46 | Customer customer = repository.findById(id); 47 | Accounts accounts = accountClient.getAccounts(id); 48 | customer = Customer.newBuilder(customer).addAllAccounts(accounts.getAccountList()).build(); 49 | return customer; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /account-service-grpc/src/test/java/pl/piomin/services/grpc/account/AccountServicesTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.grpc.account; 2 | 3 | import com.google.protobuf.Empty; 4 | import com.google.protobuf.Int32Value; 5 | import com.google.protobuf.StringValue; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.grpc.test.autoconfigure.AutoConfigureInProcessTransport; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.boot.test.context.TestConfiguration; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.grpc.client.GrpcChannelFactory; 13 | import pl.piomin.services.grpc.account.model.AccountProto; 14 | import pl.piomin.services.grpc.account.model.AccountsServiceGrpc; 15 | 16 | import static org.junit.jupiter.api.Assertions.*; 17 | 18 | @SpringBootTest 19 | @AutoConfigureInProcessTransport 20 | public class AccountServicesTests { 21 | 22 | @Autowired 23 | AccountsServiceGrpc.AccountsServiceBlockingStub service; 24 | 25 | @Test 26 | void shouldFindAll() { 27 | AccountProto.Accounts a = service.findAll(Empty.newBuilder().build()); 28 | assertNotNull(a); 29 | assertFalse(a.getAccountList().isEmpty()); 30 | } 31 | 32 | @Test 33 | void shouldFindByCustomer() { 34 | AccountProto.Accounts a = service.findByCustomer(Int32Value.newBuilder().setValue(1).build()); 35 | assertNotNull(a); 36 | assertFalse(a.getAccountList().isEmpty()); 37 | } 38 | 39 | @Test 40 | void shouldFindByNumber() { 41 | AccountProto.Account a = service.findByNumber(StringValue.newBuilder().setValue("111111").build()); 42 | assertNotNull(a); 43 | assertNotEquals(0, a.getId()); 44 | } 45 | 46 | @Test 47 | void shouldAddAccount() { 48 | AccountProto.Account a = AccountProto.Account.newBuilder() 49 | .setNumber("123456") 50 | .setCustomerId(10) 51 | .build(); 52 | 53 | a = service.addAccount(a); 54 | assertNotNull(a); 55 | assertNotEquals(0, a.getId()); 56 | } 57 | 58 | @TestConfiguration 59 | static class Config { 60 | 61 | @Bean 62 | AccountsServiceGrpc.AccountsServiceBlockingStub stub(GrpcChannelFactory channels) { 63 | return AccountsServiceGrpc.newBlockingStub(channels.createChannel("local")); 64 | } 65 | 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /account-service/src/test/java/pl/piomin/services/protobuf/account/AccountApplicationTest.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.protobuf.account; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 9 | import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter; 10 | import org.springframework.test.web.servlet.client.RestTestClient; 11 | import org.springframework.web.context.WebApplicationContext; 12 | import pl.piomin.services.protobuf.account.model.AccountProto.Account; 13 | import pl.piomin.services.protobuf.account.model.AccountProto.Accounts; 14 | 15 | @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 16 | public class AccountApplicationTest { 17 | 18 | protected Logger logger = LoggerFactory.getLogger(AccountApplicationTest.class); 19 | RestTestClient template; 20 | 21 | @BeforeEach 22 | void setUp(WebApplicationContext context) { 23 | template = RestTestClient.bindToApplicationContext(context) 24 | .baseUrl("/accounts") 25 | .configureMessageConverters(clientBuilder -> clientBuilder.addCustomConverter(new ProtobufHttpMessageConverter())) 26 | .build(); 27 | } 28 | 29 | @Test 30 | public void testFindByNumber() { 31 | template.get().uri("/{id}", "111111") 32 | .exchange() 33 | .expectStatus().is2xxSuccessful() 34 | .expectBody(Account.class); 35 | } 36 | 37 | @Test 38 | public void testFindByCustomer() { 39 | template.get().uri("/customer/{customer}", "111111") 40 | .exchange() 41 | .expectStatus().is2xxSuccessful() 42 | .expectBody(Accounts.class); 43 | } 44 | 45 | @Test 46 | public void testFindAll() { 47 | template.get().exchange() 48 | .expectStatus().is2xxSuccessful() 49 | .expectBody(Accounts.class); 50 | } 51 | 52 | 53 | // @TestConfiguration 54 | // static class Config { 55 | // 56 | // @Bean 57 | // public RestTemplateBuilder restTemplateBuilder() { 58 | // return new RestTemplateBuilder().additionalMessageConverters(new ProtobufHttpMessageConverter()); 59 | // } 60 | // 61 | // } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /customer-service-grpc/src/main/java/pl/piomin/services/grpc/customer/service/CustomersService.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.grpc.customer.service; 2 | 3 | import com.google.protobuf.Empty; 4 | import com.google.protobuf.Int32Value; 5 | import com.google.protobuf.StringValue; 6 | import io.grpc.stub.StreamObserver; 7 | import org.springframework.grpc.server.service.GrpcService; 8 | import pl.piomin.services.grpc.customer.client.AccountClient; 9 | import pl.piomin.services.grpc.customer.model.CustomerProto; 10 | import pl.piomin.services.grpc.customer.model.CustomersServiceGrpc; 11 | import pl.piomin.services.grpc.customer.repository.CustomerRepository; 12 | 13 | import java.util.List; 14 | 15 | @GrpcService 16 | public class CustomersService extends CustomersServiceGrpc.CustomersServiceImplBase { 17 | 18 | CustomerRepository repository; 19 | AccountClient accountClient; 20 | 21 | public CustomersService(CustomerRepository repository, AccountClient accountClient) { 22 | this.repository = repository; 23 | this.accountClient = accountClient; 24 | } 25 | 26 | @Override 27 | public void findById(Int32Value request, StreamObserver responseObserver) { 28 | CustomerProto.Customer c = repository.findById(request.getValue()); 29 | CustomerProto.Accounts a = accountClient.getAccountsByCustomerId(c.getId()); 30 | List l = a.getAccountList(); 31 | c = CustomerProto.Customer.newBuilder(c).addAllAccounts(l).build(); 32 | responseObserver.onNext(c); 33 | responseObserver.onCompleted(); 34 | } 35 | 36 | @Override 37 | public void findByPesel(StringValue request, StreamObserver responseObserver) { 38 | CustomerProto.Customer c = repository.findByPesel(request.getValue()); 39 | responseObserver.onNext(c); 40 | responseObserver.onCompleted(); 41 | } 42 | 43 | @Override 44 | public void findAll(Empty request, StreamObserver responseObserver) { 45 | List customerList = repository.findAll(); 46 | CustomerProto.Customers c = CustomerProto.Customers.newBuilder().addAllCustomers(customerList).build(); 47 | responseObserver.onNext(c); 48 | responseObserver.onCompleted(); 49 | } 50 | 51 | @Override 52 | public void addCustomer(CustomerProto.Customer request, StreamObserver responseObserver) { 53 | CustomerProto.Customer c = repository.add(request.getType(), request.getName(), request.getPesel()); 54 | responseObserver.onNext(c); 55 | responseObserver.onCompleted(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /account-service-grpc/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | pl.piomin 9 | sample-microservices-protobuf 10 | 1.1-SNAPSHOT 11 | 12 | 13 | account-service-grpc 14 | 1.2-SNAPSHOT 15 | 16 | 17 | ${project.artifactId} 18 | 19 | 20 | 21 | 22 | org.springframework.grpc 23 | spring-grpc-spring-boot-starter 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-web 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-actuator 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-test 41 | test 42 | 43 | 44 | org.springframework.grpc 45 | spring-grpc-test 46 | test 47 | 48 | 49 | 50 | 51 | 52 | 53 | io.github.ascopes 54 | protobuf-maven-plugin 55 | 4.1.1 56 | 57 | 4.33.1 58 | 59 | 60 | io.grpc 61 | protoc-gen-grpc-java 62 | 1.77.0 63 | @generated=omit 64 | 65 | 66 | 67 | 68 | 69 | 70 | generate 71 | 72 | 73 | 74 | 75 | 76 | org.codehaus.mojo 77 | build-helper-maven-plugin 78 | 79 | 80 | add-source 81 | generate-sources 82 | 83 | add-source 84 | 85 | 86 | 87 | target/generated-sources/protobuf 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /customer-service-grpc/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | pl.piomin 9 | sample-microservices-protobuf 10 | 1.1-SNAPSHOT 11 | 12 | 13 | customer-service-grpc 14 | 1.2-SNAPSHOT 15 | 16 | 17 | ${project.artifactId} 18 | 19 | 20 | 21 | 22 | org.springframework.grpc 23 | spring-grpc-spring-boot-starter 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-web 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-actuator 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | org.springframework.grpc 40 | spring-grpc-test 41 | test 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-test 46 | test 47 | 48 | 49 | 50 | 51 | 52 | 53 | io.github.ascopes 54 | protobuf-maven-plugin 55 | 4.1.1 56 | 57 | 4.33.1 58 | 59 | 60 | io.grpc 61 | protoc-gen-grpc-java 62 | 1.77.0 63 | @generated=omit 64 | 65 | 66 | 67 | 68 | 69 | 70 | generate 71 | 72 | 73 | 74 | 75 | 76 | org.codehaus.mojo 77 | build-helper-maven-plugin 78 | 79 | 80 | add-source 81 | generate-sources 82 | 83 | add-source 84 | 85 | 86 | 87 | target/generated-sources/protobuf 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /customer-service/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | pl.piomin 6 | sample-microservices-protobuf 7 | 1.1-SNAPSHOT 8 | 9 | customer-service 10 | 11 | 12 | ${project.artifactId} 13 | 14 | 15 | 16 | 17 | org.springframework.cloud 18 | spring-cloud-starter-openfeign 19 | 20 | 21 | org.springframework.cloud 22 | spring-cloud-starter-loadbalancer 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | com.fasterxml.jackson.core 30 | jackson-databind 31 | 32 | 33 | 34 | 35 | com.google.protobuf 36 | protobuf-java 37 | 4.33.2 38 | 39 | 40 | com.googlecode.protobuf-java-format 41 | protobuf-java-format 42 | 1.4 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-test 47 | test 48 | 49 | 50 | 51 | 52 | 53 | 54 | com.github.os72 55 | protoc-jar-maven-plugin 56 | 3.11.4 57 | 58 | 59 | generate-sources 60 | 61 | run 62 | 63 | 64 | all 65 | true 66 | 4.26.1 67 | direct 68 | src/main/generated 69 | 70 | src/main/proto 71 | 72 | 73 | 74 | 75 | 76 | 77 | org.codehaus.mojo 78 | build-helper-maven-plugin 79 | 80 | 81 | add-source 82 | generate-sources 83 | 84 | add-source 85 | 86 | 87 | 88 | src/main/generated 89 | 90 | 91 | 92 | 93 | 94 | 95 | org.jacoco 96 | jacoco-maven-plugin 97 | 0.8.14 98 | 99 | 100 | 101 | prepare-agent 102 | 103 | 104 | 105 | report 106 | test 107 | 108 | report 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /account-service/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | pl.piomin 6 | sample-microservices-protobuf 7 | 1.1-SNAPSHOT 8 | 9 | account-service 10 | 11 | 12 | ${project.artifactId} 13 | 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter-web 19 | 20 | 21 | com.fasterxml.jackson.core 22 | jackson-databind 23 | 24 | 25 | 26 | 27 | com.google.protobuf 28 | protobuf-java 29 | 4.33.2 30 | 31 | 32 | com.googlecode.protobuf-java-format 33 | protobuf-java-format 34 | 1.4 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-test 39 | test 40 | 41 | 42 | 43 | 44 | 45 | 46 | com.github.os72 47 | protoc-jar-maven-plugin 48 | 3.11.4 49 | 50 | 51 | generate-sources 52 | 53 | run 54 | 55 | 56 | all 57 | direct 58 | src/main/generated 59 | true 60 | 4.26.1 61 | 62 | src/main/proto 63 | 64 | true 65 | 66 | 67 | java 68 | src/main/generated 69 | 70 | 71 | grpc-java 72 | io.grpc:protoc-gen-grpc-java:1.63.0 73 | src/main/generated 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | org.codehaus.mojo 82 | build-helper-maven-plugin 83 | 84 | 85 | add-source 86 | generate-sources 87 | 88 | add-source 89 | 90 | 91 | 92 | src/main/generated 93 | 94 | 95 | 96 | 97 | 98 | 99 | org.jacoco 100 | jacoco-maven-plugin 101 | 0.8.14 102 | 103 | 104 | 105 | prepare-agent 106 | 107 | 108 | 109 | report 110 | test 111 | 112 | report 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Spring Boot with Protocol Buffers and gRPC [![Twitter](https://img.shields.io/twitter/follow/piotr_minkowski.svg?style=social&logo=twitter&label=Follow%20Me)](https://twitter.com/piotr_minkowski) 2 | 3 | [![CircleCI](https://circleci.com/gh/piomin/sample-microservices-protobuf.svg?style=svg)](https://circleci.com/gh/piomin/sample-microservices-protobuf) 4 | 5 | [![SonarCloud](https://sonarcloud.io/images/project_badges/sonarcloud-black.svg)](https://sonarcloud.io/dashboard?id=piomin_sample-microservices-protobuf) 6 | [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=piomin_sample-microservices-protobuf&metric=bugs)](https://sonarcloud.io/dashboard?id=piomin_sample-microservices-protobuf) 7 | [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=piomin_sample-microservices-protobuf&metric=coverage)](https://sonarcloud.io/dashboard?id=piomin_sample-microservices-protobuf) 8 | [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=piomin_sample-microservices-protobuf&metric=ncloc)](https://sonarcloud.io/dashboard?id=piomin_sample-microservices-protobuf) 9 | 10 | There are two articles related to that repo: 11 | 1. How to expose Protocol Buffers over REST. Detailed description can be found here: [Exposing Microservices over REST Protocol Buffers](https://piotrminkowski.com/2017/06/05/exposing-microservices-over-rest-protocol-buffers/) 12 | 2. How to create gRPC service with Spring Boot and integrate gRPC client with Spring Cloud discovery. Detailed description can be found here: [Introduction to gRPC with Spring Boot](https://piotrminkowski.com/2023/08/29/introduction-to-grpc-with-spring-boot/). This version has been replaced in repository with a new Spring Boot GRPC starter provided by Spring team. To check it visit https://github.com/piomin/sample-microservices-protobuf/releases/tag/v1.1. 13 | 3. Spring gRPC with Spring Boot. Detailed description can be found here: [Spring gRPC in Spring Boot](https://piotrminkowski.com/2025/12/15/grpc-spring). 14 | 15 | ## Application Architecture 16 | 17 | This project demonstrates a microservices architecture using Spring Boot with two distinct inter-service communication approaches: **REST with Protocol Buffers** and **gRPC**. The same business logic (Account and Customer management) is implemented in parallel for hands-on comparison. 18 | 19 | ### Microservices Overview 20 | 21 | The repository contains **5 Spring Boot applications** and one service discovery component: 22 | 23 | #### Service Discovery 24 | - **discovery-service** 25 | - **Port:** 8761 26 | - **Role:** Netflix Eureka server for service registration and discovery 27 | - **Dashboard URL:** http://localhost:8761 28 | 29 | #### Business Services (REST + Protocol Buffers) 30 | - **account-service** 31 | - **Port:** 2222 (configurable via `PORT` env var) 32 | - **Communication:** HTTP + Protobuf 33 | - **Proto file:** `account-service/src/main/proto/account.proto` 34 | 35 | - **customer-service** 36 | - **Port:** 3333 (configurable via `PORT` env var) 37 | - **Communication:** HTTP + Protobuf 38 | - **Proto file:** `customer-service/src/main/proto/customer.proto` 39 | 40 | #### Business Services (gRPC) 41 | - **account-service-grpc** 42 | - **HTTP Port:** 8081 43 | - **gRPC Port:** 9091 44 | - **Library:** `grpc-server-spring-boot-starter` 45 | - **Services:** `FindByNumber`, `FindByCustomer`, `FindAll`, `AddAccount` 46 | 47 | - **customer-service-grpc** 48 | - **HTTP Port:** 8081 49 | - **gRPC Port:** 9091 50 | - **Services:** `FindByPesel`, `FindById`, `FindAll`, `AddCustomer` 51 | - **Discovery:** Registers with Eureka 52 | 53 | ### Technology Stack 54 | 55 | - **Java 21** 56 | - **Spring Boot 3.4.5** 57 | - **Spring Cloud 2024.0.1** (Eureka, LoadBalancer, OpenFeign) 58 | - **Protocol Buffers 4.31.1** 59 | - **gRPC 1.63.0** 60 | - **Maven 3.8+** 61 | - **Docker & Docker Compose** (optional) 62 | 63 | ### Communication Patterns 64 | 65 | #### REST + Protobuf 66 | 67 | ``` 68 | Client → API Gateway → Service Discovery → Customer Service (HTTP:3333, Protobuf) → Account Service (HTTP:2222, Protobuf) 69 | ``` 70 | 71 | #### gRPC 72 | 73 | ``` 74 | Client → gRPC Client → Service Discovery → Customer Service gRPC (9091) → Account Service gRPC (9091) 75 | ``` 76 | 77 | #### Service Discovery Flow 78 | 79 | 1. Start **discovery-service** (8761). 80 | 2. Each microservice registers with Eureka. 81 | 3. Clients and services discover each other by querying Eureka. 82 | 4. Spring Cloud LoadBalancer handles service-side load balancing. 83 | 5. Health checks via Spring Boot Actuator endpoints. 84 | 85 | ## Running Applications Locally 86 | 87 | ### Prerequisites 88 | 89 | - **Java 21+** (`java --version`) 90 | - **Maven 3.8+** (`mvn --version`) 91 | - **Git 2+** (`git --version`) 92 | - Optional: **Docker & Docker Compose** 93 | 94 | ### Building the Applications 95 | 96 | 1. Clone the repo: 97 | ```bash 98 | git clone https://github.com/piomin/sample-microservices-protobuf.git 99 | cd sample-microservices-protobuf 100 | ``` 101 | 2. Compile (includes Protobuf & gRPC codegen): 102 | ```bash 103 | mvn clean compile 104 | ``` 105 | 3. Package JARs (skip tests for speed): 106 | ```bash 107 | mvn clean package -DskipTests 108 | ``` 109 | 110 | ### Service Startup Order 111 | 112 | > **Important:** Always start in this sequence to ensure proper registration. 113 | 114 | 1. **Discovery Service** 115 | ```bash 116 | cd discovery-service 117 | mvn spring-boot:run 118 | ``` 119 | 2. **REST + Protobuf Approach** 120 | ```bash 121 | # In parallel terminals: 122 | cd account-service && mvn spring-boot:run 123 | cd customer-service && mvn spring-boot:run 124 | ``` 125 | **OR** 126 | **gRPC Approach** 127 | ```bash 128 | cd account-service-grpc && mvn spring-boot:run 129 | cd customer-service-grpc && mvn spring-boot:run 130 | ``` 131 | 132 | ### Running with JARs 133 | 134 | ```bash 135 | # Discovery 136 | java -jar discovery-service/target/discovery-service-*.jar 137 | # Choose one approach per microservice: 138 | java -jar account-service/target/*.jar 139 | java -jar customer-service/target/*.jar 140 | # OR for gRPC: 141 | java -jar account-service-grpc/target/*.jar 142 | java -jar customer-service-grpc/target/*.jar 143 | ``` 144 | 145 | ### Verification & Testing 146 | 147 | - **Eureka Dashboard:** http://localhost:8761 148 | - **Health Checks:** 149 | ```bash 150 | curl http://localhost:8761/actuator/health 151 | curl http://localhost:2222/actuator/health 152 | curl http://localhost:3333/actuator/health 153 | curl http://localhost:9091/actuator/health 154 | ``` 155 | - **REST API Example:** 156 | ```bash 157 | curl -H "Accept: application/json" http://localhost:3333/customers 158 | ``` 159 | - **gRPC Example (using grpcurl):** 160 | ```bash 161 | grpcurl -plaintext -d '{}' localhost:9091 model.CustomersService/FindAll 162 | ``` 163 | 164 | ### Port Reference 165 | 166 | | Service | HTTP Port | gRPC Port | Env Var | Protocol | 167 | |---------------------------|-----------|-----------|---------|-----------------| 168 | | discovery-service | 8761 | – | – | HTTP | 169 | | account-service | 2222 | – | PORT | HTTP + Protobuf | 170 | | customer-service | 3333 | – | PORT | HTTP + Protobuf | 171 | | account-service-grpc | 8081 | 9091 | – | gRPC | 172 | | customer-service-grpc | 8081 | 9091 | – | gRPC | 173 | 174 | ### Troubleshooting & Tips 175 | 176 | - **Port Conflicts:** 177 | ```bash 178 | lsof -ti:8761 | xargs kill -9 179 | ``` 180 | - **Protobuf Errors:** 181 | ```bash 182 | mvn clean compile -X 183 | ``` 184 | - **Increase JVM Memory:** 185 | ```bash 186 | export MAVEN_OPTS="-Xmx2G -Xms1G" 187 | mvn spring-boot:run 188 | ``` 189 | - **Hot Reloading:** Add DevTools dependency: 190 | ```xml 191 | 192 | org.springframework.boot 193 | spring-boot-devtools 194 | runtime 195 | true 196 | 197 | ``` 198 | - **gRPC Testing:** Use `grpcurl` or BloomRPC for introspection. 199 | --------------------------------------------------------------------------------