Spring Microservices
Objective:
To develop micro services using Spring. We will also look at how to containarize it with Docker and orchestrate via kubernates.
What is a Web Service?
Restful Services with Spring Boot
@RestController
public class HelloWorldController {
//@RequestMapping(method = RequestMethod.GET, path = "/hello-world")
@GetMapping(path = "/hello-world")
public String helloWorld(){
return "Hello World";
}
@GetMapping(path = "/hello-world-bean")
public HelloWorldBean helloWorldBean(){
return new HelloWorldBean("Hello World");
}
package com.vinay.rest.webservices.vinayrestfulwebservices;
public class HelloWorldBean {
private String message;
public HelloWorldBean(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return "HelloWorldBean{" +
"message='" + message + '\'' +
'}';
}
}
{"message": "Hello World"}
logging.level.org.springframework = debug
@GetMapping(path = "/hello-world/path/{name}")
public HelloWorldBean helloWorldPathVariable(@PathVariable String name){
return new HelloWorldBean(String.format("Hello World, %s", name));
}
{"message": "Hello World, vinay"}
package com.vinay.rest.webservices.vinayrestfulwebservices.user;
import java.util.Date;
public class User {
private Integer id;
private String name;
private Date birthDate;
public User(Integer id, String name, Date birthDate) {
this.id = id;
this.name = name;
this.birthDate = birthDate;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", birthDate=" + birthDate +
'}';
}
}
package com.vinay.rest.webservices.vinayrestfulwebservices.user;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Component
public class UserDaoService {
private static List<User> users = new ArrayList<>();
private static int usersCount = 3;
static{
users.add(new User(1, "Alex", new Date()));
users.add(new User(2, "Rahim", new Date()));
users.add(new User(3, "Ram", new Date()));
}
public List<User> findAll(){
return users;
}
public User save(User user){
if(user.getId() == null){
user.setId(++usersCount);
}
users.add(user);
return user;
}
public User findOne(int id){
for(User user: users){
if(user.getId() == id){
return user;
}
}
return null;
}
}
package com.vinay.rest.webservices.vinayrestfulwebservices.user;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserResource {
@Autowired
private UserDaoService userDaoService;
@GetMapping(path = "/users")
public List<User> retrieveAllUsers(){
return userDaoService.findAll();
}
@GetMapping(path="/users/{id}")
public User retrieveUser(@PathVariable int id){
return userDaoService.findOne(id);
}
}
[{"id": 1,"name": "Alex","birthDate": "2022-07-27T15:31:46.609+00:00"},{"id": 2,"name": "Rahim","birthDate": "2022-07-27T15:31:46.609+00:00"},{"id": 3,"name": "Ram","birthDate": "2022-07-27T15:31:46.609+00:00"}]
{"id": 1,"name": "Alex","birthDate": "2022-07-27T15:31:46.609+00:00"}
@PostMapping(path = "/users")
public User createUser(@RequestBody User user){
return userDaoService.save(user);
}
protected User(){}
@PostMapping(path = "/users")
public ResponseEntity<Object> createUser(@RequestBody User user){
User savedUser = userDaoService.save(user);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(savedUser.getId()).toUri();
return ResponseEntity.created(location).build();
}
@GetMapping(path="/users/{id}")
public User retrieveUser(@PathVariable int id) throws UserNotFoundException {
User user = userDaoService.findOne(id);
if(user == null){
throw new UserNotFoundException("id-"+id);
}
return user;
}
package com.vinay.rest.webservices.vinayrestfulwebservices.user;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
package com.vinay.rest.webservices.vinayrestfulwebservices.exception;
import com.vinay.rest.webservices.vinayrestfulwebservices.user.UserNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.util.Date;
@ControllerAdvice
@RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request){
ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(UserNotFoundException.class)
public final ResponseEntity<Object> handleUserNotFoundExceptions(UserNotFoundException ex, WebRequest request){
ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
}
}
@DeleteMapping(path="/users/{id}")
public void deleteUser(@PathVariable int id) throws UserNotFoundException {
User user = userDaoService.deleteById(id);
if(user == null){
throw new UserNotFoundException("id-"+id);
}
}
public User deleteById(int id){
Iterator<User> iterator = users.iterator();
while(iterator.hasNext()){
User user = iterator.next();
if(user.getId() == id){
iterator.remove();
return user;
}
}
return null;
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
@Size(min = 2, message = "Name should have atleast 2 characters")
private String name;
@Past
private Date birthDate;
@PostMapping(path = "/users")
public ResponseEntity<Object> createUser(@Valid @RequestBody User user){
User savedUser = userDaoService.save(user);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(savedUser.getId()).toUri();
return ResponseEntity.created(location).build();
}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), "validation failed", ex.getBindingResult().toString());
return new ResponseEntity<>(exceptionResponse, HttpStatus.BAD_REQUEST);
}
@GetMapping(path="/users/{id}")
public EntityModel<User> retrieveUser(@PathVariable int id) throws UserNotFoundException {
User user = userDaoService.findOne(id);
if(user == null){
throw new UserNotFoundException("id-"+id);
}
EntityModel<User> model = EntityModel.of(user);
WebMvcLinkBuilder linkToUsers = linkTo(methodOn(this.getClass()).retrieveAllUsers());
model.add(linkToUsers.withRel("all-users"));
return model;
}
@GetMapping(path = "/hello-world-internationalized")
public String helloWorldInternationalized(@RequestHeader(name="Accept-Language", required = false)Locale locale){
return messageSource.getMessage("good.morning.message", null,
"default message", locale);//LocaleContextHolder.getLocale());
}
good.morning.message=Good Morning
good.morning.message=Bonjour
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoints.web.exposure.include=*
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-explorer</artifactId>
</dependency>
package com.vinay.rest.webservices.vinayrestfulwebservices.filtering;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(value = {"field1", "field2"})
public class SomeBean {
private String field1;
private String field2;
//@JsonIgnore
private String field3;
public SomeBean(String field1, String field2, String field3) {
this.field1 = field1;
this.field2 = field2;
this.field3 = field3;
}
public SomeBean(){}
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
public String getField2() {
return field2;
}
public void setField2(String field2) {
this.field2 = field2;
}
public String getField3() {
return field3;
}
public void setField3(String field3) {
this.field3 = field3;
}
}
package com.vinay.rest.webservices.vinayrestfulwebservices.filtering;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
@RestController
public class FilteringController {
@GetMapping(path = "/filtering")
public SomeBean retrieveSomeBean(){
return new SomeBean("value1", "value2", "value3");
}
@GetMapping(path = "/filtering-list")
public List<SomeBean> retrieveListOfSomeBean(){
return Arrays.asList(new SomeBean("value1", "value2", "value3"),
new SomeBean("value11", "value12", "value13"));
}
}
package com.vinay.rest.webservices.vinayrestfulwebservices.filtering;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
@RestController
public class FilteringController {
@GetMapping(path = "/filtering")
public MappingJacksonValue retrieveSomeBean(){
SomeBean someBean = new SomeBean("value1", "value2", "value3");
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("field1", "field2");
FilterProvider filters = new SimpleFilterProvider().addFilter("SomeBeanFilter", filter);
MappingJacksonValue mapping = new MappingJacksonValue(someBean);
mapping.setFilters(filters);
return mapping;
}
@GetMapping(path = "/filtering-list")
public MappingJacksonValue retrieveListOfSomeBean(){
List<SomeBean> list = Arrays.asList(new SomeBean("value1", "value2", "value3"),
new SomeBean("value11", "value12", "value13"));
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("field2", "field3");
FilterProvider filters = new SimpleFilterProvider().addFilter("SomeBeanFilter", filter);
MappingJacksonValue mapping = new MappingJacksonValue(list);
mapping.setFilters(filters);
return mapping;
}
}
package com.vinay.rest.webservices.vinayrestfulwebservices.filtering;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
//@JsonIgnoreProperties(value = {"field1", "field2"})
@JsonFilter("SomeBeanFilter")
public class SomeBean {
private String field1;
private String field2;
//@JsonIgnore
private String field3;
public SomeBean(String field1, String field2, String field3) {
this.field1 = field1;
this.field2 = field2;
this.field3 = field3;
}
public SomeBean(){}
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
public String getField2() {
return field2;
}
public void setField2(String field2) {
this.field2 = field2;
}
public String getField3() {
return field3;
}
public void setField3(String field3) {
this.field3 = field3;
}
}
package com.vinay.rest.webservices.vinayrestfulwebservices.versioning;
public class Person {
String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.vinay.rest.webservices.vinayrestfulwebservices.versioning;
public class PersonV2 {
private Name name;
public PersonV2() {
}
public PersonV2(Name name) {
this.name = name;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
}
package com.vinay.rest.webservices.vinayrestfulwebservices.versioning;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PersonVersionController {
@GetMapping("v1/person")
public Person person(){
return new Person("Vinay Raghumanda");
}
@GetMapping("/v2/person")
public PersonV2 personV2(){
return new PersonV2(new Name("Vinay", "Raghumanda"));
}
}
@GetMapping(value="/person/param", params = "version=1")
public Person paramV1(){
return new Person("Vinay Raghumanda");
}
@GetMapping(value="/person/param", params = "version=2")
public PersonV2 paramV2(){
return new PersonV2(new Name("Vinay", "Raghumanda"));
}
@GetMapping(value="/person/header", headers = "X-API-VERSION=1")
public Person headerV1(){
return new Person("Vinay Raghumanda");
}
@GetMapping(value="/person/header", headers = "X-API-VERSION=2")
public PersonV2 headerV2(){
return new PersonV2(new Name("Vinay", "Raghumanda"));
}
@GetMapping(value="/person/produces", produces = "application/vnd.company.app-v1+json")
public Person producesV1(){
return new Person("Vinay Raghumanda");
}
@GetMapping(value="/person/produces", produces = "application/vnd.company.app-v2+json")
public PersonV2 producesV2(){
return new PersonV2(new Name("Vinay", "Raghumanda"));
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
spring.security.user.name=user
spring.security.user.password=test
spring.jpa.show-sql=trueon Running application, you should see
spring.datasource.url=jdbc:h2:mem:testdb;NON_KEYWORDS=USER
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization=true
spring.data.jpa.repositories.bootstrap-mode=default
insert into user values(1, CURRENT_TIMESTAMP(), 'Block');
insert into user values(2, CURRENT_TIMESTAMP(), 'Chain');
insert into user values(3, CURRENT_TIMESTAMP(), 'Ethereum');
package com.vinay.rest.webservices.vinayrestfulwebservices.user;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
}
@Autowired
private UserRepository userRepository;
@GetMapping(path = "/jpa/users")
public List<User> retrieveAllUsers(){
return userRepository.findAll();
}
@GetMapping(path="/jpa/users/{id}")
public EntityModel<User> retrieveUser(@PathVariable int id) throws UserNotFoundException {
Optional<User> customUser = userRepository.findById(id);
if(!customUser.isPresent()){
throw new UserNotFoundException("id-"+id);
}
EntityModel<User> model = EntityModel.of(customUser.get());
WebMvcLinkBuilder linkToUsers = linkTo(methodOn(this.getClass()).retrieveAllUsers());
model.add(linkToUsers.withRel("all-users"));
return model;
}
@DeleteMapping(path="/jpa/users/{id}")
public void deleteUser(@PathVariable int id) throws UserNotFoundException {
userRepository.deleteById(id);
}
@PostMapping(path = "/jpa/users")
public ResponseEntity<Object> createUser(@Valid @RequestBody User customUser){
User savedCustomUser = userRepository.save(customUser);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(savedCustomUser.getId()).toUri();
return ResponseEntity.created(location).build();
}
package com.vinay.rest.webservices.vinayrestfulwebservices.user;
import javax.persistence.*;
import java.util.List;
@Entity
public class Post {
@Id
@GeneratedValue
private Integer Id;
private String description;
@ManyToOne(fetch = FetchType.LAZY)
private User user;
public Integer getId() {
return Id;
}
public void setId(Integer id) {
Id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "Post{" +
"Id=" + Id +
", description='" + description + '\'' +
'}';
}
}
@Entity
public class User {
@Id
@GeneratedValue
private Integer id;
@Size(min = 2, message = "Name should have atleast 2 characters")
private String name;
@Past
private Date birthDate;
@OneToMany(mappedBy = "user")
private List<Post> posts;
Hibernate: create table user (id integer not null, birth_date timestamp, name varchar(255), primary key (id))
insert into user values(1001, CURRENT_TIMESTAMP(), 'Block');
insert into user values(1002, CURRENT_TIMESTAMP(), 'Chain');
insert into user values(1003, CURRENT_TIMESTAMP(), 'Ethereum');
insert into post values(11001, 'First Post', 1001);
insert into post values(11002, 'Second Post', 1001);
@GetMapping(path = "/jpa/users/{id}/posts")
public List<Post> retrieveAllPosts(@PathVariable int id){
Optional<User> userOp = userRepository.findById(id);
if(!userOp.isPresent()){
throw new UserNotFoundException("id-"+id);
}
return userOp.get().getPosts();
}
@ManyToOne(fetch = FetchType.LAZY)
@JsonIgnore
private User user;
@PostMapping(path = "/jpa/users/{id}/posts")
public ResponseEntity<Object> createPost(@PathVariable int id, @RequestBody Post post){
Optional<User> userOp = userRepository.findById(id);
if(!userOp.isPresent()){
throw new UserNotFoundException("id-"+id);
}
post.setUser(userOp.get());
postRepository.save(post);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(post.getId()).toUri();
return ResponseEntity.created(location).build();
}
package com.vinay.rest.webservices.vinayrestfulwebservices.user;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PostRepository extends JpaRepository<Post, Integer> {
}
Quick Introduction to Micro services
Micro services with Spring cloud
spring.config.import=optional:configserver:http://localhost:88882. Creating hard coded limits service
package com.vinay.microservices.limitservice.controller;
import com.vinay.microservices.limitservice.bean.Limit;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LimitController {
@GetMapping("/limits")
public Limit retrieveAllLimits(){
return new Limit(1, 1000);
}
}
package com.vinay.microservices.limitservice.bean;invoke GET call --> http://localhost:8080/limits
public class Limit {
private Integer minimum;
private Integer maximum;
public Limit() {
}
public Limit(Integer minimum, Integer maximum) {
this.minimum = minimum;
this.maximum = maximum;
}
public Integer getMinimum() {
return minimum;
}
public void setMinimum(Integer minimum) {
this.minimum = minimum;
}
public Integer getMaximum() {
return maximum;
}
public void setMaximum(Integer maximum) {
this.maximum = maximum;
}
@Override
public String toString() {
return "Limit{" +
"minimum=" + minimum +
", maximum=" + maximum +
'}';
}
}
spring.config.import=optional:configserver:http://localhost:8888
limit-service.minimum=2
limit-service.maximum=998
package com.vinay.microservices.limitservice.configuration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties("limit-service")
public class Configuration {
private int maximum;
private int minimum;
public int getMaximum() {
return maximum;
}
public void setMaximum(int maximum) {
this.maximum = maximum;
}
public int getMinimum() {
return minimum;
}
public void setMinimum(int minimum) {
this.minimum = minimum;
}
}
package com.vinay.microservices.limitservice.controller;4. Setting up spring cloud config server
import com.vinay.microservices.limitservice.bean.Limit;
import com.vinay.microservices.limitservice.configuration.Configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LimitController {
@Autowired
private Configuration configuration;
@GetMapping("/limits")
public Limit retrieveAllLimits(){
return new Limit(configuration.getMinimum(), configuration.getMaximum());
}
}
spring.application.name=spring-cloud-config-server
server.port=8888
spring.cloud.config.server.git.uri=file:///Users/vraghuma/FSE/git-localconfig-repoannotate application with EnableConfigServer
@EnableConfigServerRun the application and invoke - http://localhost:8888/limit-service/default
@SpringBootApplication
public class SpringCloudConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudConfigServerApplication.class, args);
}
}
"name": "limit-service",
"profiles": [
"default"
],
"label": null,
"version": "bbc112a18cc861b128447911c485ee6d7973136e",
"state": null,
"propertySources": [
{
"name": "file:///Users/vraghuma/FSE/git-localconfig-repo/limit-service.properties",
"source": {
"limit-service.minimum": "4",
"limit-service.maximum": "990"
}
}
]
}
spring.application.name=limit-serviceinvoke localhost:8080/limits
spring.config.import=optional:configserver:http://localhost:8888
limit-service.minimum=2
limit-service.maximum=998
"minimum": 4,
"maximum": 990
}
2022-08-03 00:11:48.585 INFO 7414 --- [ restartedMain] o.s.c.c.c.ConfigServerConfigDataLoader : Located environment: name=limit-service, profiles=[default], label=null, version=bbc112a18cc861b128447911c485ee6d7973136e, state=null
spring.profiles.active=qa
spring.cloud.config.profile=qa
2022-08-05 18:15:21.872 INFO 37860 --- [ restartedMain] o.s.c.c.c.ConfigServerConfigDataLoader : Located environment: name=limit-service, profiles=[dev], label=null, version=bbc112a18cc861b128447911c485ee6d7973136e, state=null
spring.application.name=currency-exchange-service10. Let's create a simple a simple hard coded currency exchange microservice url
spring.config.import=optional:configserver:http://localhost:8888
server.port=8000
package com.vinay.microservices.currencyexchangeservice;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
@RestController
public class CurrencyExchangeController {
@GetMapping("/currency-exchange/from/{from}/to/{to}")
public CurrencyExchange retrieveExchangeValue(@PathVariable String from,
@PathVariable String to){
return new CurrencyExchange(1000L, from, to, BigDecimal.valueOf(50));
}
}
package com.vinay.microservices.currencyexchangeservice;GET - http://localhost:8000/currency-exchange/from/USD/to/INR
import java.math.BigDecimal;
public class CurrencyExchange {
private Long id;
private String from;
private String to;
private BigDecimal conversionMultiple;
public CurrencyExchange() {
}
public CurrencyExchange(Long id, String from, String to, BigDecimal conversionMultiple) {
this.id = id;
this.from = from;
this.to = to;
this.conversionMultiple = conversionMultiple;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public BigDecimal getConversionMultiple() {
return conversionMultiple;
}
public void setConversionMultiple(BigDecimal conversionMultiple) {
this.conversionMultiple = conversionMultiple;
}
@Override
public String toString() {
return "CurrencyExchange{" +
"id=" + id +
", from='" + from + '\'' +
", to='" + to + '\'' +
", conversionMultiple=" + conversionMultiple +
'}';
}
}
private String environment;Use Environmet class to get port
@Autowired
private Environment environment;
@GetMapping("/currency-exchange/from/{from}/to/{to}")
public CurrencyExchange retrieveExchangeValue(@PathVariable String from,
@PathVariable String to){
CurrencyExchange currencyExchange = new CurrencyExchange(1000L, from, to, BigDecimal.valueOf(50));
currencyExchange.setEnvironment(environment.getProperty("local.server.port"));
return currencyExchange;
}
<dependency>Add this dependency to interact with H2 database
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>Let's create our own database rather depending on random database
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
spring.jpa.show-sql=trueAccess H2 database with http://localhost:8000/h2-console
spring.datasource.url=jdbc:h2:mem:testdb
spring.h2.console.enabled=true
@EntityObserve @Column annotation, as from is a key word in sql, it throws an error, so we are renaming the column in database.
public class CurrencyExchange {
@Id
private Long id;
@Column(name="currency_from")
private String from;
@Column(name="currency_to")
private String to;
private BigDecimal conversionMultiple;
private String environment;
ID | CONVERSION_MULTIPLE | ENVIRONMENT | CURRENCY_FROM | CURRENCY_TO |
---|
spring.jpa.defer-datasource-initialization=truedata.sql
insert into currency_exchange13. Update REST API to connect to our database
(id, currency_from, currency_to, conversion_multiple, environment)
values(10001, 'USD', 'INR', 65, '');
insert into currency_exchange
(id, currency_from, currency_to, conversion_multiple, environment)
values(10002, 'EUR', 'INR', 75, '');
insert into currency_exchange
(id, currency_from, currency_to, conversion_multiple, environment)
values(10003, 'AUD', 'INR', 25, '');
package com.vinay.microservices.currencyexchangeservice;Lets autowire it in CurrencyExchangeController
import org.springframework.data.jpa.repository.JpaRepository;
public interface CurrencyExchangeRepository extends JpaRepository<CurrencyExchange, Long> {
}
@RestControllerLet's create one method in Repository that filters data based on from and to values
public class CurrencyExchangeController {
@Autowired
private CurrencyExchangeRepository currencyExchangeRepository;
CurrencyExchange findByFromAndTo(String from, String to);JPA will provide the query
@GetMapping("/currency-exchange/from/{from}/to/{to}")14. Let's try creating Currency Conversion Microservice
public CurrencyExchange retrieveExchangeValue(@PathVariable String from,
@PathVariable String to){
CurrencyExchange currencyExchange = currencyExchangeRepository.findByFromAndTo(from, to);
currencyExchange.setEnvironment(environment.getProperty("local.server.port"));
return currencyExchange;
}
spring.appication.name=currency-conversionLet's create controller
server.port=8100
spring.config.import=optional:configserver:http://localhost:8888
package com.vinay.micrservices.currencyconversionservice;and a Page Object
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
@RestController
public class CurrencyConversionController {
@GetMapping("/currency-conversion/from/{from}/to/{to}/quantity/{quantity}")
public CurrencyConversion calculateCurrencyConversion(
@PathVariable String from,
@PathVariable String to,
@PathVariable BigDecimal quantity
){
return new CurrencyConversion(10001L, from, to, quantity, BigDecimal.ONE, BigDecimal.ONE, "" );
}
}
package com.vinay.micrservices.currencyconversionservice;15. Lets invoke currency exchange service from currency conversion service
import java.math.BigDecimal;
public class CurrencyConversion {
private Long id;
private String from;
private String to;
private BigDecimal quantity;
private BigDecimal conversionMultiple;
private BigDecimal totalCalculatedAmount;
private String environment;
public CurrencyConversion() {
}
public CurrencyConversion(Long id, String from, String to, BigDecimal quantity, BigDecimal conversionMultiple, BigDecimal totalCalculatedAmount, String environment) {
this.id = id;
this.from = from;
this.to = to;
this.conversionMultiple = conversionMultiple;
this.quantity = quantity;
this.totalCalculatedAmount = totalCalculatedAmount;
this.environment = environment;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public BigDecimal getConversionMultiple() {
return conversionMultiple;
}
public void setConversionMultiple(BigDecimal conversionMultiple) {
this.conversionMultiple = conversionMultiple;
}
public BigDecimal getQuantity() {
return quantity;
}
public void setQuantity(BigDecimal quantity) {
this.quantity = quantity;
}
public BigDecimal getTotalCalculatedAmount() {
return totalCalculatedAmount;
}
public void setTotalCalculatedAmount(BigDecimal totalCalculatedAmount) {
this.totalCalculatedAmount = totalCalculatedAmount;
}
public String getEnvironment() {
return environment;
}
public void setEnvironment(String environment) {
this.environment = environment;
}
}
@GetMapping("/currency-conversion/from/{from}/to/{to}/quantity/{quantity}")API: http://localhost:8100/currency-conversion/from/USD/to/INR/quantity/3
public CurrencyConversion calculateCurrencyConversion(
@PathVariable String from,
@PathVariable String to,
@PathVariable BigDecimal quantity
) {
HashMap<String, String> uriVariables = new HashMap<>();
uriVariables.put("from", from);
uriVariables.put("to", to);
ResponseEntity<CurrencyConversion> responseEntity = new RestTemplate().getForEntity(
"http://localhost:8000/currency-exchange/from/USD/to/INR",
CurrencyConversion.class,
uriVariables
);
CurrencyConversion currencyConversion = responseEntity.getBody();
return new CurrencyConversion(
currencyConversion.getId(),
from,
to,
quantity,
currencyConversion.getConversionMultiple(),
quantity.multiply(currencyConversion.getConversionMultiple()),
currencyConversion.getEnvironment());
}
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@FeignClient(name = "currency-exchange-service", url = "localhost:8000")Update application to enable Feign clients
public interface CurrencyExchangeProxy {
@GetMapping("/currency-exchange/from/{from}/to/{to}")
public CurrencyConversion retrieveExchangeValue(@PathVariable String from,
@PathVariable String to);
}
@SpringBootApplicationIn controller
@EnableFeignClients
public class CurrencyConversionServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CurrencyConversionServiceApplication.class, args);
}
}
@GetMapping("/currency-conversion-feign/from/{from}/to/{to}/quantity/{quantity}")Make sure you're auto wiring currencyExchangeProxy;
public CurrencyConversion calculateCurrencyConversionFeign(
@PathVariable String from,
@PathVariable String to,
@PathVariable BigDecimal quantity
) {
CurrencyConversion currencyConversion = currencyExchangeProxy.retrieveExchangeValue(from, to);
return new CurrencyConversion(
currencyConversion.getId(),
from,
to,
quantity,
currencyConversion.getConversionMultiple(),
quantity.multiply(currencyConversion.getConversionMultiple()),
currencyConversion.getEnvironment());
}
package com.vinay.microservices.namingserver;Make sure to annoatate with EnableEurekaServer
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class NamingServerApplication {
public static void main(String[] args) {
SpringApplication.run(NamingServerApplication.class, args);
}
}
spring.application.name=naming-serverRun the application and eureka server will be running on port 8761 --> localhost:8761
server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
<dependency>now both the services will be registered with Eureka naming server
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eurekawhich will allow us to configure different eureka servers
package com.vinay.micrservices.currencyconversionservice;Let's try running feign service from 8000 and 8001 port and see the behavior and how it's dynamically handled by eureka server
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
//@FeignClient(name = "currency-exchange-service", url = "localhost:8000")
@FeignClient(name = "currency-exchange-service")
public interface CurrencyExchangeProxy {
@GetMapping("/currency-exchange/from/{from}/to/{to}")
public CurrencyConversion retrieveExchangeValue(@PathVariable String from,
@PathVariable String to);
}
spring.application.name=api-gatewayStart the server, api gateway will be registered with Eureka.
server.port=8765
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=truefor lowercase use above property--> CURRENCY-EXCHANGE-SERVICE --> currency-exchange-service which looks like below
package com.vinay.microservices.apigateway;you can have your headers and Parameters added
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApiGatewayConfiguration {
@Bean
public RouteLocator gatewayRouter(RouteLocatorBuilder builder){
return builder.routes().route(
p -> p.path("/get")
.filters(f->f
.addRequestHeader("MyHeader", "MyURI")
.addRequestParameter("Param", "MyValue"))
.uri("http://httpbin.org:80"))
.build();
}
}
#spring.cloud.gateway.discovery.locator.enabled=true
#spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
package com.vinay.microservices.apigateway;for a input of currency-exchange, load balance and pick up the naming server name.
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApiGatewayConfiguration {
@Bean
public RouteLocator gatewayRouter(RouteLocatorBuilder builder){
return builder.routes().
route(p -> p.path("/get")
.filters(f->f
.addRequestHeader("MyHeader", "MyURI")
.addRequestParameter("Param", "MyValue"))
.uri("http://httpbin.org:80")).
route(p -> p.path("/currency-exchange/**")
.uri("lb://currency-exchange-service")).
route(p -> p.path("/currency-conversion/**")
.uri("lb://currency-conversion"))
.build();
}
}
route(p -> p.path("/currency-conversion-new/**")http://localhost:8765/currency-conversion-new/from/USD/to/INR/quantity/3
.filters(f->f.rewritePath(
"/currency-conversion-new/(?<segment>.*)",
"/currency-conversion-feign/${segment}"
))
.uri("lb://currency-conversion"))
package com.vinay.microservices.apigateway;you may see the logs like below
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class LoggingFilter implements GlobalFilter {
private Logger logger = LoggerFactory.getLogger(LoggingFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
logger.info("path of request received -> {}",exchange.getRequest().getPath());
return chain.filter(exchange);
}
}
2022-08-14 00:41:59.545 INFO 18568 --- [ctor-http-nio-3] c.v.m.apigateway.LoggingFilter : path of request received -> /currency-conversion/from/USD/to/INR/quantity/10
2022-08-14 00:42:03.238 INFO 18568 --- [ctor-http-nio-3] c.v.m.apigateway.LoggingFilter : path of request received -> /currency-exchange/from/USD/to/INR
<dependency>Let's create a simple api to play with
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
</dependency>
package com.vinay.microservices.currencyexchangeservice;25. Play with Resilience 4J - Retry and Fallback methods
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CircuitBreakerController {
@GetMapping("/sample-api")
public String sampleAPI(){
return "Sample API";
}
}
package com.vinay.microservices.currencyexchangeservice;Logs:
import io.github.resilience4j.retry.annotation.Retry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class CircuitBreakerController {
private Logger logger = LoggerFactory.getLogger(CircuitBreakerController.class);
@GetMapping("/sample-api")
@Retry(name = "default")
public String sampleAPI(){
logger.info("Retrying .. ");
ResponseEntity<String> forEntity = new RestTemplate().getForEntity("http://localhost:8080/dummy",
String.class);
return forEntity.getBody();
}
}
2022-08-14 01:31:22.731 INFO 25811 --- [nio-8000-exec-1] c.v.m.c.CircuitBreakerController : Retrying ..
2022-08-14 01:31:23.236 INFO 25811 --- [nio-8000-exec-1] c.v.m.c.CircuitBreakerController : Retrying ..
package com.vinay.microservices.currencyexchangeservice;we have a fallback response
import io.github.resilience4j.retry.annotation.Retry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class CircuitBreakerController {
private Logger logger = LoggerFactory.getLogger(CircuitBreakerController.class);
@GetMapping("/sample-api")
@Retry(name = "sample-api", fallbackMethod = "hardCodedResponse")
public String sampleAPI(){
logger.info("Retrying .. ");
ResponseEntity<String> forEntity = new RestTemplate().getForEntity("http://localhost:8080/dummy",
String.class);
return forEntity.getBody();
}
public String hardCodedResponse(Exception ex){
return "fallback response";
}
}
spring.application.name=currency-exchange-servicewaitDuration is wait time for every retry
spring.config.import=optional:configserver:http://localhost:8888
server.port=8000
spring.jpa.show-sql=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization=true
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
resilience4j.retry.instances.sample-api.maxRetryAttempts=5
resilience4j.retry.instances.sample-api.waitDuration=1s
resilience4j.retry.instances.sample-api.enableExponentialBackOff=true
package com.vinay.microservices.currencyexchangeservice;How do i know when microservice is up?
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class CircuitBreakerController {
private Logger logger = LoggerFactory.getLogger(CircuitBreakerController.class);
@GetMapping("/sample-api")
// @Retry(name = "sample-api", fallbackMethod = "hardCodedResponse")
@CircuitBreaker(name = "default", fallbackMethod = "hardCodedResponse")
public String sampleAPI(){
logger.info("Retrying .. ");
ResponseEntity<String> forEntity = new RestTemplate().getForEntity("http://localhost:8080/dummy",
String.class);
return forEntity.getBody();
}
public String hardCodedResponse(Exception ex){
return "fallback response";
}
}
package com.vinay.microservices.currencyexchangeservice;properties should be
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import io.github.resilience4j.retry.annotation.Retry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class CircuitBreakerController {
private Logger logger = LoggerFactory.getLogger(CircuitBreakerController.class);
@GetMapping("/sample-api")
// @Retry(name = "sample-api", fallbackMethod = "hardCodedResponse")
// @CircuitBreaker(name = "default", fallbackMethod = "hardCodedResponse")
@RateLimiter(name="default")
public String sampleAPI(){
/*logger.info("Retrying .. ");
ResponseEntity<String> forEntity = new RestTemplate().getForEntity("http://localhost:8080/dummy",
String.class);
return forEntity.getBody();*/
return "sample-api";
}
public String hardCodedResponse(Exception ex){
return "fallback response";
}
}
resilience4j.ratelimiter.instances.default.limitForPeriod=2which means allowing 2 requests over 10 seconds.
resilience4j.ratelimiter.instances.default.limitRefreshPeriod=10s
resilience4j.bulkhead.instances.default.maxConcurrentCalls=10
@Bulkhead(name="default")
public String sampleAPI(){
Docker with Microservices
b9f63b2f1b50 in28min/todo-rest-api-h2:1.0.0.RELEASE "sh -c 'java $JAVA_O…" 4 minutes ago Up 4 minutes 0.0.0.0:5000->5000/tcp pedantic_swartz
in28min/todo-rest-api-h2 1.0.0.RELEASE 9d05dd98f4a4 2 months ago 143MB
<dependency>which allows to create id that can be tracked across all services.
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>all the services send info to distributed server, if ds is down, so that's why Rabbit MQ, which will queue and sends it to ds.
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
spring.sleuth.sampler.probability=1.0
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>vinay/mmv-${project.artifactId}:${project.version}</name>
</image>
<pullPolicy>IF_NOT_PRESENT</pullPolicy>
</configuration>
</plugin>
</plugins>
</build>
<configuration>create a image by running mvn spring-boot:build-image -DskipTests
<image>
<name>vinay/mmv-${project.artifactId}:${project.version}</name>
</image>
<pullPolicy>IF_NOT_PRESENT</pullPolicy>
</configuration>
Kubernates with microservices
- AWS Elastic Container Service (ECS)
- Cloud Nuetral - Kubernates
Client Version: version.Info{Major:"1", Minor:"24", GitVersion:"v1.24.3", GitCommit:"aef86a93758dc3cb2c658dd9657ab4ad4afc21cb", GitTreeState:"clean", BuildDate:"2022-07-13T14:30:46Z", GoVersion:"go1.18.3", Compiler:"gc", Platform:"linux/amd64"}
Kustomize Version: v4.5.4
Server Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.10-gke.600", GitCommit:"2c921d7b040ed9c5a3a1f9407fb109b74d72d0a4", GitTreeState:"clean", BuildDate:"2022-06-02T09:20:24Z", GoVersion:"go1.16.15b7", Compiler:"gc", Platform:"linux/amd64"}
WARNING: version difference between client (1.24) and server (1.22) exceeds the supported minor version skew of +/-1
hello-world-rest-api-9d764b4dc-gffb6 1/1 Running 0 17m
hello-world-rest-api-9d764b4dc 1 1 1 19m
NAME READY UP-TO-DATE AVAILABLE AGE
hello-world-rest-api 1/1 1 1 21m
kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-world-rest-api LoadBalancer 10.104.129.174 34.170.252.132 8080:30516/TCP 17m
kubernetes ClusterIP 10.104.128.1 <none> 443/TCP 45m
deployment.apps/hello-world-rest-api scaled
vinay_raghu10@cloudshell:~ (symbolic-voyage-359519)$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
hello-world-rest-api-9d764b4dc-6pw25 0/1 Pending 0 4s <none> <none> <none> <none>
hello-world-rest-api-9d764b4dc-gffb6 1/1 Running 0 32h 10.104.0.130 gk3-vinay-autopilot-clus-default-pool-203925f2-4fpg <none> <none>
hello-world-rest-api-9d764b4dc-hg948 0/1 Pending 0 4s <none> <none> <none> <none>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.vinay.micrservices</groupId>
<artifactId>currency-conversion-service</artifactId>
<version>0.0.11-SNAPSHOT</version> <!-- KUBERNETES CHANGE -->
<name>currency-conversion-service-kubernetes</name> <!-- KUBERNETES CHANGE -->
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- KUBERNETES CHANGE -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!-- KUBERNETES CHANGE -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>-->
<!-- KUBERNETES CHANGE -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>vinay/mmv-${project.artifactId}:${project.version}</name>
</image>
<pullPolicy>IF_NOT_PRESENT</pullPolicy>
</configuration>
</plugin>
</plugins>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.vinay.micrservices</groupId>
<artifactId>currency-conversion-service</artifactId>
<version>0.0.11-SNAPSHOT</version> <!-- KUBERNETES CHANGE -->
<name>currency-conversion-service-kubernetes</name> <!-- KUBERNETES CHANGE -->
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- KUBERNETES CHANGE -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!-- KUBERNETES CHANGE -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>-->
<!-- KUBERNETES CHANGE -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>vinay/mmv-${project.artifactId}:${project.version}</name>
</image>
<pullPolicy>IF_NOT_PRESENT</pullPolicy>
</configuration>
</plugin>
</plugins>
</build>
</project>
//Change Kubernetes
logger.info("calculateCurrencyConversion called with {} to {} with {}", from, to, quantity);
package com.vinay.microservices.currencyexchangeservice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CurrencyExchangeController {
private Logger logger = LoggerFactory.getLogger(CurrencyExchangeController.class);
@Autowired
private CurrencyExchangeRepository currencyExchangeRepository;
@Autowired
private Environment environment;
@GetMapping("/currency-exchange/from/{from}/to/{to}")
public CurrencyExchange retrieveExchangeValue(@PathVariable String from,
@PathVariable String to){
logger.info("retrieveExchangeValue called with {} to {}", from, to);
CurrencyExchange currencyExchange = currencyExchangeRepository.findByFromAndTo(from, to);
if (currencyExchange == null) {
throw new RuntimeException("No records found");
}
// CHANGE KUBERNETES
String port = environment.getProperty("local.server.port");
String host = environment.getProperty("HOSTNAME");
String version = "v11";
currencyExchange.setEnvironment(port+" "+version+" "+host);
return currencyExchange;
}
}
#CHANGE KUBERNETES
management.endpoint.health.probes.enabled=true
management.health.livenessState.enabled=true
management.health.readinessState.enabled=true
package com.vinay.micrservices.currencyconversionservice;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
//@FeignClient(name = "currency-exchange-service", url = "localhost:8000")
@FeignClient(name = "currency-exchange-service", url = "${CURRENCY_EXCHANGE_SERVICE_HOST:http://localhost}:8000")
//@FeignClient(name = "currency-exchange-service")
public interface CurrencyExchangeProxy {
@GetMapping("/currency-exchange/from/{from}/to/{to}")
public CurrencyConversion retrieveExchangeValue(@PathVariable String from,
@PathVariable String to);
}
<configuration>
<image>
<name><<docker_id>>/mmv-${project.artifactId}:${project.version}</name>
</image>
<pullPolicy>IF_NOT_PRESENT</pullPolicy>
</configuration>
kubectl create deployment currency-exchange --image=vinayraghu/mmv-currency-exchange-service:0.0.11-SNAPSHOT
W0819 11:42:46.609811 37955 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead.
To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke
Warning: Autopilot set default resource requests for Deployment default/currency-exchange, as resource requests were not specified. See http://g.co/gke/autopilot-defaults.
deployment.apps/currency-exchange created
kubectl expose deployment currency-exchange --type=LoadBalancer --port=8000
W0819 11:43:49.893846 38128 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead.
To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke
service/currency-exchange exposed
kubectl get services
W0819 11:46:46.265926 38654 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead.
To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
currency-exchange LoadBalancer 10.104.130.115 34.66.148.201 8000:30411/TCP 2m53s
hello-world-rest-api LoadBalancer 10.104.129.174 34.170.252.132 8080:30516/TCP 3d10h
kubernetes ClusterIP 10.104.128.1 <none> 443/TCP 3d10h
kubectl get deployment currency-exchange -o yaml >> deployment.yaml
kubectl get service currency-exchange -o yaml >> service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
autopilot.gke.io/resource-adjustment: '{"input":{"containers":[{"name":"mmv-currency-exchange-service"}]},"output":{"containers":[{"limits":{"cpu":"500m","ephemeral-storage":"1Gi","memory":"2Gi"},"requests":{"cpu":"500m","ephemeral-storage":"1Gi","memory":"2Gi"},"name":"mmv-currency-exchange-service"}]},"modified":true}'
deployment.kubernetes.io/revision: "1"
creationTimestamp: "2022-08-19T06:50:29Z"
generation: 1
labels:
app: currency-exchange
name: currency-exchange
namespace: default
resourceVersion: "2960943"
uid: 0d0b96d8-e8fc-44cf-b328-023098749058
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: currency-exchange
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: currency-exchange
spec:
containers:
- image: vinayraghu/mmv-currency-exchange-service:0.0.11-SNAPSHOT
imagePullPolicy: IfNotPresent
name: mmv-currency-exchange-service
resources:
limits:
cpu: 500m
ephemeral-storage: 1Gi
memory: 2Gi
requests:
cpu: 500m
ephemeral-storage: 1Gi
memory: 2Gi
securityContext:
capabilities:
drop:
- NET_RAW
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext:
seccompProfile:
type: RuntimeDefault
terminationGracePeriodSeconds: 30
status:
conditions:
- lastTransitionTime: "2022-08-19T06:50:29Z"
lastUpdateTime: "2022-08-19T06:50:33Z"
message: ReplicaSet "currency-exchange-55fb6ddfcc" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
- lastTransitionTime: "2022-08-19T07:44:00Z"
lastUpdateTime: "2022-08-19T07:44:00Z"
message: Deployment does not have minimum availability.
reason: MinimumReplicasUnavailable
status: "False"
type: Available
observedGeneration: 1
replicas: 1
unavailableReplicas: 1
updatedReplicas: 1
---
apiVersion: v1
kind: Service
metadata:
annotations:
cloud.google.com/neg: '{"ingress":true}'
creationTimestamp: "2022-08-19T06:52:18Z"
finalizers:
- service.kubernetes.io/load-balancer-cleanup
labels:
app: currency-exchange
name: currency-exchange
namespace: default
resourceVersion: "2929858"
uid: 573ba524-8f42-42ca-b46d-9a9ca40f03bf
spec:
allocateLoadBalancerNodePorts: true
clusterIP: 10.104.128.131
clusterIPs:
- 10.104.128.131
externalTrafficPolicy: Cluster
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- nodePort: 31970
port: 8000
protocol: TCP
targetPort: 8000
selector:
app: currency-exchange
sessionAffinity: None
type: LoadBalancer
status:
loadBalancer:
ingress:
kubectl apply -f deployment.yaml
containers:
- image: vinayraghu/mmv-currency-exchange-service:0.0.11-SNAPSHOT
imagePullPolicy: IfNotPresent
name: mmv-currency-exchange-service
env:
- name: CURRENCY_EXCHANGE_URI
value: http://currency-exchange
Comments
Post a Comment