From a5c275c7476c502d1c318615d643441b88cddd60 Mon Sep 17 00:00:00 2001 From: Adrian Calavie Date: Thu, 5 May 2022 19:15:53 +0300 Subject: [PATCH] Eureka + Gateway --- .../example/discovery/DiscoveryServer.java | 28 ++-- .../src/main/resources/discovery-server.yml | 7 + .../com/eureka/persons/PersonsController.java | 148 ++++++++++-------- .../com/eureka/persons/PersonsServer.java | 28 ++-- .../persons/services/PersonServiceImpl.java | 2 +- .../src/main/resources/persons-server.yml | 6 + lab-6-api-gateway/api-gateway-project/pom.xml | 18 +-- .../ApiGatewayProjectApplication.java | 5 +- .../src/main/resources/application.properties | 1 + .../src/main/resources/application.yml | 29 +++- lab-6-api-gateway/pom.xml | 5 + lab-6-api-gateway/service1/pom.xml | 14 +- .../example/service1/Service1Application.java | 22 ++- .../src/main/resources/application.yml | 13 +- lab-6-api-gateway/service2/pom.xml | 16 +- .../java/com/example/service2/Product.java | 13 ++ .../example/service2/Service2Application.java | 31 +++- .../repository/ProductRepository.java | 27 ++++ .../src/main/resources/application.yml | 12 +- 19 files changed, 294 insertions(+), 131 deletions(-) create mode 100644 lab-6-api-gateway/api-gateway-project/src/main/resources/application.properties create mode 100644 lab-6-api-gateway/service2/src/main/java/com/example/service2/Product.java create mode 100644 lab-6-api-gateway/service2/src/main/java/com/example/service2/repository/ProductRepository.java diff --git a/eureka/discovery-server/src/main/java/com/example/discovery/DiscoveryServer.java b/eureka/discovery-server/src/main/java/com/example/discovery/DiscoveryServer.java index 15d783c..01cad77 100644 --- a/eureka/discovery-server/src/main/java/com/example/discovery/DiscoveryServer.java +++ b/eureka/discovery-server/src/main/java/com/example/discovery/DiscoveryServer.java @@ -4,22 +4,24 @@ import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; import java.io.IOException; @SpringBootApplication +@EnableEurekaServer public class DiscoveryServer { - - private static Logger logger = LoggerFactory.getLogger(DiscoveryServer.class); - - public static void main(String... args) throws IOException { - // Look for configuration in discovery-server.properties or discovery-server.yml - System.setProperty("spring.config.name", "discovery-server"); - - var ctx = SpringApplication.run(DiscoveryServer.class, args); - assert (ctx != null); - logger.info("Started ..."); - System.in.read(); - ctx.close(); - } + + private static Logger logger = LoggerFactory.getLogger(DiscoveryServer.class); + + public static void main(String... args) throws IOException { + // Look for configuration in discovery-server.properties or discovery-server.yml + System.setProperty("spring.config.name", "discovery-server"); + + var ctx = SpringApplication.run(DiscoveryServer.class, args); + assert (ctx != null); + logger.info("Started ..."); + System.in.read(); + ctx.close(); + } } diff --git a/eureka/discovery-server/src/main/resources/discovery-server.yml b/eureka/discovery-server/src/main/resources/discovery-server.yml index b55b43b..8d23338 100644 --- a/eureka/discovery-server/src/main/resources/discovery-server.yml +++ b/eureka/discovery-server/src/main/resources/discovery-server.yml @@ -3,6 +3,13 @@ spring: name: discovery-service # Configure this Discovery Server #TODO here you add configurations for server +server: + port: 3000 + +eureka: + client: + register-with-eureka: false + fetch-registry: false logging: pattern: diff --git a/eureka/persons-server/src/main/java/com/eureka/persons/PersonsController.java b/eureka/persons-server/src/main/java/com/eureka/persons/PersonsController.java index ff4acb5..75fc8a8 100644 --- a/eureka/persons-server/src/main/java/com/eureka/persons/PersonsController.java +++ b/eureka/persons-server/src/main/java/com/eureka/persons/PersonsController.java @@ -1,5 +1,6 @@ package com.eureka.persons; +import com.eureka.persons.ex.NotFoundException; import com.eureka.persons.person.Person; import com.eureka.persons.services.PersonService; import org.springframework.http.HttpStatus; @@ -7,71 +8,94 @@ import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; -import java.util.ArrayList; +import java.util.Comparator; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; @RestController @RequestMapping("/persons") public class PersonsController { - private PersonService personService; - - public PersonsController(PersonService personService) { - this.personService = personService; - } - - /** - * Handles requests to list all persons. - */ - //TODO find all persons using the functions already implemented and sort them by id - @ResponseStatus(HttpStatus.OK) - @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) - public List list() { - return new ArrayList<>(); - } - - /** - * Handles requests to create a person. - */ - //TODO save a person to the db or throw PersonsException - @ResponseStatus(HttpStatus.CREATED) - @PostMapping - public void create(@RequestBody Person person, BindingResult result) { - } - - /** - * Returns the {@code Person} instance with id {@code id} - * - * @param id - * @return - */ - //TODO find a person by id or throw NotFoundException - @ResponseStatus(HttpStatus.OK) - @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) - public Person show(@PathVariable Long id) { - return new Person(); - } - - /** - * Updates the {@code Person} instance with id {@code id} - * - * @param updatedPerson - * @param id - * @return - */ - //TODO update an existing person if found else throw NotFoundException - @ResponseStatus(HttpStatus.NO_CONTENT) - @PutMapping("/{id}") - public void update(@RequestBody Person updatedPerson, @PathVariable Long id) { - } - - /** - * Delete the {@code Person} instance with id {@code id} - * - * @param id - */ - //TODO delete a person - @ResponseStatus(HttpStatus.NO_CONTENT) - @DeleteMapping("/{id}") - public void delete(@PathVariable Long id) { - } + private PersonService personService; + + public PersonsController(PersonService personService) { + this.personService = personService; + } + + /** + * Handles requests to list all persons. + */ + //TODO find all persons using the functions already implemented and sort them by id + @ResponseStatus(HttpStatus.OK) + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public List list() { + return personService + .findAll().stream() + .sorted(Comparator.comparing(Person::getId)) + .collect(Collectors.toList()); + } + + /** + * Handles requests to create a person. + */ + //TODO save a person to the db or throw PersonsException + @ResponseStatus(HttpStatus.CREATED) + @PostMapping + public void create(@RequestBody Person person, BindingResult result) { + if (result.hasErrors()) + throw new PersonsException(HttpStatus.BAD_REQUEST, "Person couldn't be added!"); + + personService.save(person); + } + + /** + * Returns the {@code Person} instance with id {@code id} + * + * @param id + * @return + */ + //TODO find a person by id or throw NotFoundException + @ResponseStatus(HttpStatus.OK) + @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + public Person show(@PathVariable Long id) { + var personIdQuery = personService.findById(id); + if (personIdQuery.isEmpty()) + throw new NotFoundException(Person.class, id); + + return personIdQuery.get(); + } + + /** + * Updates the {@code Person} instance with id {@code id} + * + * @param updatedPerson + * @param id + * @return + */ + //TODO update an existing person if found else throw NotFoundException + @ResponseStatus(HttpStatus.NO_CONTENT) + @PutMapping("/{id}") + public void update(@RequestBody Person updatedPerson, @PathVariable Long id) { + var personIdQuery = personService.findById(id); + + personIdQuery.ifPresent(person -> person = updatedPerson); + // else + throw new NotFoundException(Person.class, id); + } + + /** + * Delete the {@code Person} instance with id {@code id} + * + * @param id + */ + //TODO delete a person + @ResponseStatus(HttpStatus.NO_CONTENT) + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + var personIdQuery = personService.findById(id); + if (personIdQuery.isEmpty()) + throw new NotFoundException(Person.class, id); + + personService.delete(personIdQuery.get()); + } } \ No newline at end of file diff --git a/eureka/persons-server/src/main/java/com/eureka/persons/PersonsServer.java b/eureka/persons-server/src/main/java/com/eureka/persons/PersonsServer.java index f2098ae..5764a84 100644 --- a/eureka/persons-server/src/main/java/com/eureka/persons/PersonsServer.java +++ b/eureka/persons-server/src/main/java/com/eureka/persons/PersonsServer.java @@ -5,23 +5,25 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import java.io.IOException; @EntityScan(basePackages = "com.eureka.persons") @SpringBootApplication +@EnableEurekaClient public class PersonsServer { - - private static Logger logger = LoggerFactory.getLogger(PersonsServer.class); - - public static void main(String... args) throws IOException { - // Look for configuration in persons-server.properties or persons-server.yml - System.setProperty("spring.config.name", "persons-server"); - - var ctx = SpringApplication.run(PersonsServer.class, args); - assert (ctx != null); - logger.info("Started ..."); - System.in.read(); - ctx.close(); - } + + private static final Logger logger = LoggerFactory.getLogger(PersonsServer.class); + + public static void main(String... args) throws IOException { + // Look for configuration in persons-server.properties or persons-server.yml + System.setProperty("spring.config.name", "persons-server"); + + var ctx = SpringApplication.run(PersonsServer.class, args); + assert (ctx != null); + logger.info("Started ..."); + System.in.read(); + ctx.close(); + } } diff --git a/eureka/persons-server/src/main/java/com/eureka/persons/services/PersonServiceImpl.java b/eureka/persons-server/src/main/java/com/eureka/persons/services/PersonServiceImpl.java index f1c2fec..6c4c0c9 100644 --- a/eureka/persons-server/src/main/java/com/eureka/persons/services/PersonServiceImpl.java +++ b/eureka/persons-server/src/main/java/com/eureka/persons/services/PersonServiceImpl.java @@ -11,7 +11,7 @@ @Service @Transactional public class PersonServiceImpl implements PersonService { - private PersonRepo personRepo; + private final PersonRepo personRepo; public PersonServiceImpl(PersonRepo personRepo) { this.personRepo = personRepo; diff --git a/eureka/persons-server/src/main/resources/persons-server.yml b/eureka/persons-server/src/main/resources/persons-server.yml index b2b466d..5f367ae 100644 --- a/eureka/persons-server/src/main/resources/persons-server.yml +++ b/eureka/persons-server/src/main/resources/persons-server.yml @@ -25,6 +25,12 @@ server: # Discovery Server Access #TODO here you add configurations for eureka client +eureka: + client: + service-url: + defaultZone: http://localhost:3000/eureka + fetch-registry: true + register-with-eureka: true info: app: diff --git a/lab-6-api-gateway/api-gateway-project/pom.xml b/lab-6-api-gateway/api-gateway-project/pom.xml index 871ce8d..5eaa00a 100644 --- a/lab-6-api-gateway/api-gateway-project/pom.xml +++ b/lab-6-api-gateway/api-gateway-project/pom.xml @@ -3,12 +3,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - org.springframework.boot - spring-boot-starter-parent - 2.6.5 - + lab-6-api-gateway + com.example + 0.0.1-SNAPSHOT - com.example api-gateway-project 0.0.1-SNAPSHOT api-gateway-project @@ -18,7 +16,6 @@ 2021.0.1 - org.springframework.boot @@ -33,16 +30,15 @@ spring-cloud-starter-gateway - - org.springframework.boot - spring-boot-starter-test - test - io.projectreactor reactor-test test + + org.springframework.cloud + spring-cloud-netflix-eureka-server + diff --git a/lab-6-api-gateway/api-gateway-project/src/main/java/com/example/apigatewayproject/ApiGatewayProjectApplication.java b/lab-6-api-gateway/api-gateway-project/src/main/java/com/example/apigatewayproject/ApiGatewayProjectApplication.java index e0f6114..e976e83 100644 --- a/lab-6-api-gateway/api-gateway-project/src/main/java/com/example/apigatewayproject/ApiGatewayProjectApplication.java +++ b/lab-6-api-gateway/api-gateway-project/src/main/java/com/example/apigatewayproject/ApiGatewayProjectApplication.java @@ -2,12 +2,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication public class ApiGatewayProjectApplication { - + public static void main(String[] args) { SpringApplication.run(ApiGatewayProjectApplication.class, args); } - + } diff --git a/lab-6-api-gateway/api-gateway-project/src/main/resources/application.properties b/lab-6-api-gateway/api-gateway-project/src/main/resources/application.properties new file mode 100644 index 0000000..66403d2 --- /dev/null +++ b/lab-6-api-gateway/api-gateway-project/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.main.web-application-type=reactive \ No newline at end of file diff --git a/lab-6-api-gateway/api-gateway-project/src/main/resources/application.yml b/lab-6-api-gateway/api-gateway-project/src/main/resources/application.yml index aaad35d..085e160 100644 --- a/lab-6-api-gateway/api-gateway-project/src/main/resources/application.yml +++ b/lab-6-api-gateway/api-gateway-project/src/main/resources/application.yml @@ -1,6 +1,33 @@ server: port: 8080 + #TODO use eureka to discover the URL for the service1 and service2 #TODO configure spring cloud gateway to route the request to downstream services (service1 and service2) based on the paths(/api/greeting, /product) #TODO for greeting endpoint add a route to accept requests to /greeting but before calling service1 it must append api before the greeting path (HINT: rewrite path filter) -#and method types (GET,POST) \ No newline at end of file +#and method types (GET,POST) +spring: + application: + name: gateway + cloud: + gateway: + routes: + - id: service1 + uri: http://localhost:8081 + predicates: + - Path=/greeting/** + filters: + - RewritePath=/greeting, /api/greeting/ + - id: service2 + uri: http://localhost:8082 + predicates: + - Path=/product/** + - id: persons + uri: http://localhost:4001 + predicates: + - Path=/persons/** +eureka: + client: + service-url: + defaultZone: http://localhost:3000/eureka + fetch-registry: true + register-with-eureka: true \ No newline at end of file diff --git a/lab-6-api-gateway/pom.xml b/lab-6-api-gateway/pom.xml index aa491ab..491b65e 100644 --- a/lab-6-api-gateway/pom.xml +++ b/lab-6-api-gateway/pom.xml @@ -23,6 +23,11 @@ service2 + + org.projectlombok + lombok + + org.springframework.boot spring-boot-starter diff --git a/lab-6-api-gateway/service1/pom.xml b/lab-6-api-gateway/service1/pom.xml index 478fe3f..89f8966 100644 --- a/lab-6-api-gateway/service1/pom.xml +++ b/lab-6-api-gateway/service1/pom.xml @@ -3,12 +3,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - org.springframework.boot - spring-boot-starter-parent - 2.6.5 - + lab-6-api-gateway + com.example + 0.0.1-SNAPSHOT - com.example service1 0.0.1-SNAPSHOT service1 @@ -30,11 +28,9 @@ org.springframework.boot spring-boot-starter-web - - org.springframework.boot - spring-boot-starter-test - test + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client diff --git a/lab-6-api-gateway/service1/src/main/java/com/example/service1/Service1Application.java b/lab-6-api-gateway/service1/src/main/java/com/example/service1/Service1Application.java index 27128b5..e89d29f 100644 --- a/lab-6-api-gateway/service1/src/main/java/com/example/service1/Service1Application.java +++ b/lab-6-api-gateway/service1/src/main/java/com/example/service1/Service1Application.java @@ -1,20 +1,34 @@ package com.example.service1; +import lombok.extern.java.Log; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.eureka.EnableEurekaClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; @SpringBootApplication +@EnableEurekaClient +@RestController public class Service1Application { - + public static void main(String[] args) { SpringApplication.run(Service1Application.class, args); } - + + @GetMapping("api/greeting") + public String greet(@RequestHeader Map headers, @RequestParam String name) { + System.out.println(headers); + return "Hello " + name; + } // TODO // 1. define a GET endpoint /api/greeting which should accept a query parameter "name" // 2. return should be a string returning a greeting: Hello Brasov // 3. print request headers // 4. register the service in eureka - - + } diff --git a/lab-6-api-gateway/service1/src/main/resources/application.yml b/lab-6-api-gateway/service1/src/main/resources/application.yml index 54b155f..644b4ea 100644 --- a/lab-6-api-gateway/service1/src/main/resources/application.yml +++ b/lab-6-api-gateway/service1/src/main/resources/application.yml @@ -1,2 +1,13 @@ server: - port: 8081 \ No newline at end of file + port: 8081 + +spring: + application: + name: service1 + +eureka: + client: + service-url: + defaultZone: http://localhost:3000/eureka + fetch-registry: true + register-with-eureka: true \ No newline at end of file diff --git a/lab-6-api-gateway/service2/pom.xml b/lab-6-api-gateway/service2/pom.xml index be06918..b7d3748 100644 --- a/lab-6-api-gateway/service2/pom.xml +++ b/lab-6-api-gateway/service2/pom.xml @@ -3,12 +3,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - org.springframework.boot - spring-boot-starter-parent - 2.6.5 - + lab-6-api-gateway + com.example + 0.0.1-SNAPSHOT - com.example service2 0.0.1-SNAPSHOT service2 @@ -26,15 +24,9 @@ org.springframework.boot spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test diff --git a/lab-6-api-gateway/service2/src/main/java/com/example/service2/Product.java b/lab-6-api-gateway/service2/src/main/java/com/example/service2/Product.java new file mode 100644 index 0000000..a92ee16 --- /dev/null +++ b/lab-6-api-gateway/service2/src/main/java/com/example/service2/Product.java @@ -0,0 +1,13 @@ +package com.example.service2; + +import lombok.*; + +@Data +@Setter +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class Product { + String name; + int quantity; +} diff --git a/lab-6-api-gateway/service2/src/main/java/com/example/service2/Service2Application.java b/lab-6-api-gateway/service2/src/main/java/com/example/service2/Service2Application.java index 3c2fbd2..5db7dbc 100644 --- a/lab-6-api-gateway/service2/src/main/java/com/example/service2/Service2Application.java +++ b/lab-6-api-gateway/service2/src/main/java/com/example/service2/Service2Application.java @@ -1,14 +1,42 @@ package com.example.service2; +import com.example.service2.repository.ProductRepository; +import org.apache.http.HttpStatus; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.eureka.EnableEurekaClient; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; @SpringBootApplication +@EnableEurekaClient +@RestController public class Service2Application { - + + private final ProductRepository productRepository; + + public Service2Application(ProductRepository productRepository) { + this.productRepository = productRepository; + } + public static void main(String[] args) { SpringApplication.run(Service2Application.class, args); } + + + @PostMapping("product") + public ResponseEntity postProduct(@RequestBody Product product) { + try { + var prod = productRepository.saveProduct(product); + return ResponseEntity.ok(prod); + } catch (RuntimeException exception) { + return ResponseEntity.status(HttpStatus.SC_BAD_REQUEST).body(exception.getMessage()); + } + } + // TODO // 1. define a POST endpoint /product which should accept a request body containing two properties -product name and quantity //2. save the request body in memory @@ -17,3 +45,4 @@ public static void main(String[] args) { // 5. register the service in eureka // 6. define a GET endpoint /product to return the saved data using the POST endpoint - return type is List } + diff --git a/lab-6-api-gateway/service2/src/main/java/com/example/service2/repository/ProductRepository.java b/lab-6-api-gateway/service2/src/main/java/com/example/service2/repository/ProductRepository.java new file mode 100644 index 0000000..f3202c2 --- /dev/null +++ b/lab-6-api-gateway/service2/src/main/java/com/example/service2/repository/ProductRepository.java @@ -0,0 +1,27 @@ +package com.example.service2.repository; + +import com.example.service2.Product; +import org.springframework.stereotype.Repository; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Repository +public class ProductRepository { + List products = new ArrayList<>(); + + public Optional findProductByName(String name) { + return products.stream().filter(product -> product.getName().equals(name)).findFirst(); + } + + public Product saveProduct(Product product) { + findProductByName(product.getName()).ifPresent(r -> { + throw new RuntimeException("Product with name " + r.getName() + " already exists!"); + }); + + products.add(product); + + return product; + } +} diff --git a/lab-6-api-gateway/service2/src/main/resources/application.yml b/lab-6-api-gateway/service2/src/main/resources/application.yml index 4772153..99027f1 100644 --- a/lab-6-api-gateway/service2/src/main/resources/application.yml +++ b/lab-6-api-gateway/service2/src/main/resources/application.yml @@ -1,3 +1,13 @@ server: - port: 8082 \ No newline at end of file + port: 8082 +spring: + application: + name: service2 + +eureka: + client: + service-url: + defaultZone: http://localhost:3000/eureka + fetch-registry: true + register-with-eureka: true \ No newline at end of file