Rest, Mikroserwisy, Spring Cloud - rlip/java GitHub Wiki

Rest

Code Meaning
200 Ok Successful requests other than creations and deletions.
201 Created Successful creation of a queue, topic, temporary queue, temporary topic, session, producer, consumer, listener, queue browser, or message.
204 No Content Successful deletion of a queue, topic, session, producer, or listener.
400 Bad Request The path info doesn't have the right format, or a parameter or request body value doesn't have the right format, or a required parameter is missing, or values have the right format but are invalid in some way (for example, destination parameter does not exist, content is too big, or client ID is in use).
403 Forbidden The invoker is not authorized to invoke the operation.
404 Not Found The object referenced by the path does not exist.
405 Method Not Allowed The method is not one of those allowed for the path.
409 Conflict An attempt was made to create an object that already exists.
500 Internal Server Error The execution of the service failed in some way.

GET

200-ok, 404-not found

POST - tworzenie nowych

201 - jeśli utworzy, 200 (OK) or 204 (No Content)

PUT - aktualizacja pełna (zastąpienie) ### PATH - aktualizacja częściowa

201 - jeśli utworzy, jeśli zmodyfikowany: 200 (OK) - ze statusem or 204 (No Content) -bez treści

DELETE

200 (OK) - jeśli zawiera status, 202 - Accepted - jeśli usuwanie w kolejce, 204 (No Content) - jeśli nic nie zawiera, 404 (NOT FOUND) jeśli nie ma

Consumer first Liczba mnoga w nazwach np. users

Spring Cloud Config

https://cloud.spring.io/spring-cloud-config/reference/html/

Server

Przechowuje konfiguracje mikroserwisów i to dla różnych środowisk Potrzebny jest:

  • klasa cloud server np. z springboota
  • repozytorium gita
  • spring.appication.name, server.port i spring.cloud.config.server.git.url
  • @EnableConfigServer przy @SpringBootApplication
  • zapisujemy w gicie nasze propertisy, domyślne + osobno dla każdego środowiska
spring.application.name=spring-cloud-config-server
server.port=8888
spring.cloud.config.server.git.uri=file://C:/p/spring-microservices/git-localconfig-repo
////
@EnableConfigServer
@SpringBootApplication
public class SpringCloudConfigServerApplication {
////
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-config-server</artifactId>
		</dependency>

Client

Trzeba zrobić normalnie klasę do odczytu parametrów, oraz:

spring.application.name=limits-service
spring.cloud.config.uri=http://localhost:8888
spring.profiles.active=dev
///
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-config</artifactId>
		</dependency>

Feign proxy

Zrobienie feign proxy zamiast rest template

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-feign</artifactId>
			<version>1.4.7.RELEASE</version>
		</dependency>
///////
@FeignClient(name="currency-exchange-service", url="localhost:8000")
public interface CurrencyExchangeServiceProxy {
    @GetMapping("/currency-exchange/from/{from}/to/{to}")
    CurrencyConversionBeen retrieveExchangeValue(@PathVariable String from, @PathVariable String to);
}
//////

  @Autowired
    CurrencyExchangeServiceProxy currencyExchangeServiceProxy;
   // i w metodzie:
    CurrencyConversionBeen response = currencyExchangeServiceProxy.retrieveExchangeValue(from, to);
//////
Nowsze:
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
--
 @Configuration
 @EnableFeignClients
 public RestClientConfiguration { }
--
@FeignClient("ingredient-service")
public interface IngredientClient {
   @GetMapping("/ingredients/{id}")
   Ingredient getIngredient(@PathVariable("id") String id);
}

Ribbon Load Balancer

spring-cloud-starter-netflix-eureka-client to też dodaje ribona

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-ribbon</artifactId>
			<version>1.4.7.RELEASE</version>
		</dependency>
////////

@FeignClient(name="currency-exchange-service")
@RibbonClient(name="currency-exchange-service")
public interface CurrencyExchangeServiceProxy {
    @GetMapping("/currency-exchange/from/{from}/to/{to}")
    CurrencyConversionBeen retrieveExchangeValue(@PathVariable String from, @PathVariable String to);
}
////
currency-exchange-service.ribbon.listOfServers=http://localhost:8000,http://localhost:8001,http://localhost:8002

Eureka Name Server

Server

spring.application.name=netflix-eureka-naming-server
server.port=8761
eureka.client.register-with-eureka=false    //czy zarejestrwowąć się jako usługa na innych egzemplarzach eureki
eureka.client.fetch-registry=false   //czy pobierać rejestr z innych eurek (to i to wyżej, jest po to żeby niebyło błędu przy lokalnym uruchomieniu)
--
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
		</dependency>

////
@EnableEurekaServer
@SpringBootApplication
public class NetflixEurekaNamingServerApplication {

Klienci

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka</artifactId>  //nazwa z netflix jest nowsza
			<version>1.4.7.RELEASE</version>
		</dependency>
--------------nowsze:
        <properties>
           <spring-cloud.version>Finchley.SR1</spring-cloud.version>
        </properties>
---
        <dependency>       
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
/////
eureka.client.service-url.default-zone=http://localhost:8761/eureka/
/////
@EnableDiscoveryClient
@SpringBootApplication
public class CurrencyConversionServiceApplication {

eureka.server.enableSelfPreservation=false - wyłącza tryb utrzymania egzemplarza (serwisy powinny odnawiać rejestrację co 30 sekund, jak nie zrobią tego 3 razy albo usługa jest utrzymywana (przy true), lub wyrejestrowana (przy false). True jest dobre na produkcję, żeby w przypadku chwilowych problemów z siecią nie wyrejestrowywać serwisów.

Netflix Zuul Api Gateway

jak przez nią będa przechodzic wszystkie requesty to można tu sobie zrobic jakieś logowanie, autoryzację, itp

Server

można wywołać go przez http://localhost:8761/{nazwa_serwisu}/{url_serwisu}

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
		</dependency>
////
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class NetflixZuulApiGatewayServerApplication {
////
@Component
public class ZuulLoggingFilter extends ZuulFilter {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    @Override
    public String filterType() {
        return "pre"; //"pre","post","error"
    }
    @Override
    public int filterOrder() {
        return 1;
    }
    @Override
    public boolean shouldFilter() {
        return true;
    }
    @Override
    public Object run()  {
        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
        logger.info("request -> {} request uri -> {}", request, request.getRequestURI());
        return null;
    }
}

Klient

Jak używamy Feign proxy to trzeba podać w @FeignClient nazwę zuula, a w @GetMapping trzeba dodać nazwę serwisu przed urlem

@FeignClient(name="netflix-zuul-api-gateway-server")
@RibbonClient(name="currency-exchange-service")
public interface CurrencyExchangeServiceProxy {
    @GetMapping("/currency-exchange-service/currency-exchange/from/{from}/to/{to}")
    CurrencyConversionBeen retrieveExchangeValue(@PathVariable String from, @PathVariable String to);
}

Spring Cloud Sleuth - nadawanie indentyfikatorów requestom

W wersji co miałem wystarczyło dodać coś takiego, żeby requesty miały id

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-sleuth</artifactId>
            <version>${spring-cloud-sleuth.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-sleuth</artifactId>
    </dependency>
</dependencies>

Actuator

Dużo różnych narzedzi monitorujących. Dobrze uruchomić /actuator ze stron głównej przez Hal Browser

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>

Hal Browser

Przeglądarka requestów REST - standardowo dostępna w głównej ścieżce

		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-rest-hal-browser</artifactId>
		</dependency>

Hystrics Fault Tolerance

Można zrobić, że jak będzie jakieś runtime exception to zwróci jakieś domyślne wartości

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-hystrix</artifactId>
			<version>1.4.7.RELEASE</version>
		</dependency>
////////////////
    @GetMapping("/fault-tolerance-example")
    @HystrixCommand(fallbackMethod = "fallbackRetrieveConfiguration")
    public LimitConfiguration retrieveLimitConfigurations() {
        throw new RuntimeException("Not available");
    }

    public LimitConfiguration fallbackRetrieveConfiguration() {
        return new LimitConfiguration(9, 999);
    }

Odpytanie innych serwisów

Rest template

        Map<String, String> uriVariables = new HashMap<>();
        uriVariables.put("from", from);
        uriVariables.put("to", to);

        ResponseEntity<CurrencyConversionBeen> responseEntity = new RestTemplate().getForEntity(
                "http://localhost:8000/currency-exchange/from/{from}/to/{to}",
                CurrencyConversionBeen.class,
                uriVariables);

        CurrencyConversionBeen response = responseEntity.getBody();

Hateoas - linki w odpowiedzi json

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-hateoas</artifactId>
		</dependency>
//////////
	@GetMapping("/users")
	public List<User> retrieveAllUsers() {
		return service.findAll();
	}

	@GetMapping("/users/{id}")
	public Resource<User> retrieveUser(@PathVariable int id) {
		User user = service.findOne(id);
		Resource<User> resource = new Resource<>(user);
		ControllerLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllUsers());
		resource.add(linkTo.withRel("all-users"));
		return resource;
	}

Swagger - dokumentacja

/swagger-ui.html --html /v2/api-docs --json

swagger-codegen-maven-plugin - pozwala na wygenerowanie klienta api z jsona

		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.4.0</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>  // to jest dla strony html
			<version>2.4.0</version>
		</dependency>
/////////////
@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket api() {  //to służy do ustawiania opisów dokumentacji
        return new Docket(DocumentationType.SWAGGER_2)
         .select()
         .paths(PathSelector.ant("/api/**"))  //tu można wymienić jakie ścieżki nas interesują, jeśli nie wszystkie
         .apis(RequestHandlerSelectors.basePackage("com.commarch.custormer.api.rest")) // to jak wyżej, tylko ogranicza do pakietu
         .build()
         .apiInfo(createApiInfo()));
        
    }

    private createApiInfo(){
          return ApiInfo(
           "Customer API",
           "Customer database",
           "1.01",
           "comarch.com/termsOfService",
           new Contact(
                      "Roman L",
                      "comarch.com",
                      "[email protected]"
           ),
           "licencja",
           "comarch.com/licence"
          )
    }
}
///////////////
//to nie obowiązkowe, dodaje do opisu
@GetMaping
@ApiOperation(value = "Find customer by id", notes = "Resturns by given id")
public Customer getCustomer(@ApiParam(value= "Unique id of customer", example = "145") @PathVariable int id) { 
}

Filtrowanie

Statyczne

@JsonIgnoreProperties(value={"field1","field2"}) //nad encją
@JsonIgnore   //nad zmienną

Dynamiczne

@JsonFilter("SomeBeanFilter") //nad encją

/////////
    @GetMapping("/filtering")
    public MappingJacksonValue retrieveSomeBen() {
        SomeBean someBean = new SomeBean("value1", "value2", "value3");
        MappingJacksonValue mapping = new MappingJacksonValue(someBean);

        SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("field1", "field3");
        FilterProvider filters = new SimpleFilterProvider().addFilter("SomeBeanFilter", filter);
        mapping.setFilters(filters);
        return mapping;
    }

    @GetMapping("/filtering-list")
    public MappingJacksonValue retrieveListOSomeBen() {
        List<SomeBean> list = Arrays.asList(
                new SomeBean("value1", "value2", "value3"),
                new SomeBean("value21", "value22", "value23")
        );
        MappingJacksonValue mapping = new MappingJacksonValue(list);
        SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("field1", "field3");
        FilterProvider filters = new SimpleFilterProvider().addFilter("SomeBeanFilter", filter);
        mapping.setFilters(filters);
        return mapping;
    }

Wersjonowanie

Te w adresie można cachować

//    Można tak
//    @GetMapping(value="/v1/person")
//    @GetMapping(value="/v2/person")

    /////Params -/person/param?version=1
    @GetMapping(value="/person/param", params="version=1")
    public PersonV1 paramV1() {
        return new PersonV1("Bob Charlie");
    }
    @GetMapping(value="/person/param", params="version=2")
    public PersonV2 paramV2() {
        return new PersonV2(new Name("Bob", "Charlie"));
    }

    ////Headers - po prostu do hedera
    @GetMapping(value="/person/header", headers="X-API-VERSION=1")
    public PersonV1 headerV1() {
        return new PersonV1("Bob Charlie");
    }
    @GetMapping(value="/person/header", headers="X-API-VERSION=2")
    public PersonV2 headerV2() {
        return new PersonV2(new Name("Bob", "Charlie"));
    }

    ////Produces - to do Accept Header
    @GetMapping(value="/person/produces", produces="application/vnd.company.app-v1+json")
    public PersonV1 producesV1() {
        return new PersonV1("Bob Charlie");
    }
    @GetMapping(value="/person/produces",  produces="application/vnd.company.app-v2+json")
    public PersonV2 producesV2() {
        return new PersonV2(new Name("Bob", "Charlie"));
    }

Basic Authentication

standardowo po dodaniu dependecy spring security włacza się basic auth z loginem user i hasłem podawanym w konsoli, można to zmienić ustawiająć w propertisach:

spring.security.user.name=username
spring.security.user.password=123456

management.secutiry.enabled // to włacza auth w Actuatorze
///////
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>

Exception Handling

jak nie ma danego zasobu można zrobić np throw new CostamNotFofoundException("id = " + id)
i adnotacjąc ustawić 404

@ResponseStatus(HttpStatus.NOT_FOUND)
public class CostamNotFofoundExceptionextends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}

Zamiast tego lepiej globalnie ustandaryzować zwracane exceptiony (zrobic klasę i kontroller przechwytujący):

@ControllerAdvice  //dodaje mettody dostepne we innych controlerach
@RestController
public class CustomizesResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Object> handleAllException(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> handleUserNotFoundException(Exception ex, WebRequest request) {
        ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
        return new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
    }
}
//////////
public class ExceptionResponse {
    private Date timestamp;
    private String message;
    private String details;

    public ExceptionResponse(Date timestamp, String message, String details) {
        this.timestamp = timestamp;
        this.message = message;
        this.details = details;
    }
///getery, setery

Walidacja

	@PostMapping("/users")
//poniżej dodać @Valid
	public ResponseEntity<Object> createUser(@Valid @RequestBody User user){ 
/////
public class User {
	//przez message - można dodać swoją wiadomość
	@Size(min=2, message="Name should have at least 2 characters") 
	private String name;

	@Past
	private Date birthDate;
/////
//tak można ustawić co ma być zwracane gdy m=będzie bład
@ControllerAdvice  
@RestController
public class CustomizesResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @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);
    }

Spring Cloud Contract

https://www.facebook.com/jvaconpoland/videos/755994141837289/

⚠️ **GitHub.com Fallback** ⚠️