I try to configure feign client connect timeout, as defined on official documentation, but it does not work.
The app is just simple demo app.
MyFeign.java
package com.example.demo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
#FeignClient(name = "myFeign", url = "localhost:9047/payroll", configuration = FeignConfig.class)
public interface MyFeign {
#GetMapping(value = "/api/master/{employee_id}")
ResponseEntity<String> disableMasterPayroll(#PathVariable(name = "employee_id") String employeeId);
}
1st attempt : using configuration class
FeignConfig.java
package com.example.demo;
import org.springframework.context.annotation.Configuration;
import feign.Request;
#Configuration
public class FeignConfig {
public Request.Options feignRequestOptionsCustom() {
return new Request.Options(5000, 5000);
}
}
2nd attempt, using application.yml as defined on spring doc (copy-paste it)
feign:
hystrix:
enabled: false
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
But none works.
Found several question on stackoverflow (this, this), but none resolve it.
I did not use hystrix. This is my gradle file
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
I'm using boot 2.2 with spring cloud Hoxton.RELEASE.
Any idea how to solve this?
Related
Assuming the Spring Boot app is connected to a database which has all the Camunda related tables created like dbo.ACT_HI_PROCINST, dbo.ACT_HI_TASKINST, dbo.ACT_RU_TASK, dbo.ACT_RU_EXECUTION, etc. There is a UI screen (Angular/React frontend) which accepts BPMN & DMN files to be deployed in Camunda database (could be any Relational Database). This (.bpmn/.dmn) should get deployed as the latest version of their file name.
Camunda Version:
7.13.0
User can select a file by browsing the file system from UI screen (assuming UI file browsing and selecting a valid .bpmn or .dmn is done). User has to send this selected file as part of request in REST call to Spring Boot app, and deploy the .bpmn/.dmn file in database.
A .bpmn or .dmn could be deployed in many ways:
via Camunda Modeler
via Spring Boot App startup auto-deploy from /src/main/resources/bpmn/* by using #EnableProcessApplication annotation along with #SpringBootApplication
via Manual deployment by sending .bpmn/.dmn file from frontend UI via REST call to Spring Boot controller and using Camunda's RepositoryService.createDeployment()
application.yaml:
Provide Camunda DB datasource configurations like url, username and password along with Camunda admin user credential like -
camunda.bpm.admin-user:
id: admin
password: admin
camunda:
bpm:
history-level: audit
job-execution:
core-pool-size: 3
max-pool-size: 10
jpa:
enabled: true
handle-transaction: true
server:
port: 8091
spring:
application:
name: camunda-bpm-workflow-app
datasource:
url: jdbc:sqlserver://ABC250.abc.xyz:10010;databaseName=DevCamunda
username: DevUser
password: DevPass
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
hikari:
maximum-pool-size: 200
jpa:
hibernate:
ddl-auto: create
show-sql: false
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.SQLServerDialect
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
pom.xml:
camunda-bpm-spring-boot-starter
camunda-bpm-spring-boot-starter-webapp
camunda-bpm-spring-boot-starter-rest
Auto-deployment: #EnableProcessApplication: Spring Boot App startup auto-deploy from /src/main/resources/bpmn/*
Turn your Spring Boot app into a Camunda process application using #EnableProcessApplication annotation without extending any super-classes.
In the Spring Boot app, this annotation helps to link a resource to the process engine.
Disables the SpringProcessEngineConfiguration auto-deploy feature
Enables auto-deployment from /src/main/resources/META-INF/processes.xml
processes.xml - is an indicator for resource scanning. This could be left empty.
We can have .bpmn and .dmn files inside bmpn folder, example - /src/main/resources/bpmn/xyz.bmpn or /src/main/resources/bpmn/abc.dmn
On startup of Spring Boot app, all .bpmn and .dmn files from location /src/main/resources/bpmn/* will be auto-deployed if any changes were done in them
Manual deployment: By sending .bpmn/.dmn file from fronend UI via REST call to Spring Boot controller and using Camunda's RepositoryService.createDeployment(). After deployment is successful, new deployment ID can be found in database using SQL:
SELECT
ID_,
NAME_,
DEPLOY_TIME_,
SOURCE_,
TENANT_ID_
FROM
dbo.ACT_RE_DEPLOYMENT
ORDER BY
DEPLOY_TIME_ DESC
Main Class:
import org.camunda.bpm.spring.boot.starter.annotation.EnableProcessApplication;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
#EnableProcessApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
REST Controller endpoint:
A Camunda BPMN or DMN file to be deployed in database will be consumed by the rest endpoint as MediaType.MULTIPART_FORM_DATA_VALUE.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.abc.workflow.dto.CamundaDeployResponse;
import com.abc.workflow.service.CamundaDeployService;
import lombok.extern.slf4j.Slf4j;
#RestController("/camunda")
public class CamundaDeployController {
#Autowired
private CamundaDeployService camundaDeployService;
#PostMapping(value = "/deploy", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<CamundaDeployResponse> deploy(#RequestParam("file") MultipartFile file) {
return ResponseEntity.ok(camundaDeployService.deploy(file));
}
}
Service Interface:
import org.springframework.web.multipart.MultipartFile;
import com.abc.workflow.dto.CamundaDeployResponse;
public interface CamundaDeployService {
CamundaDeployResponse deploy(MultipartFile file);
}
Service Implementation Class:
RepositoryService - Service providing access to the repository of process definitions and deployments.
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.repository.DeploymentWithDefinitions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.abc.workflow.dto.CamundaDeployRequestDto;
import com.abc.workflow.dto.CamundaDeployResponse;
import com.abc.workflow.service.CamundaDeployService;
import lombok.extern.slf4j.Slf4j;
#Slf4j
#Service
public class CamundaDeployServiceImpl implements CamundaDeployService {
#Autowired
private RepositoryService repositoryService;
#Override
public CamundaDeployResponse deploy(MultipartFile file) {
CamundaDeployResponse camundaDeployResponse = new CamundaDeployResponse();
String orgFileName = file.getOriginalFilename();
log.info("Camunda file to be deployed [{}]", orgFileName);
if(orgFileName.endsWith(".bpmn") || orgFileName.endsWith(".dmn")) {
try {
log.info("Camunda Deployment START : [{}]", orgFileName);
DeploymentWithDefinitions d = null;
d = repositoryService.createDeployment().addInputStream(orgFileName, file.getInputStream()).name(orgFileName).deployWithResult();
camundaDeployResponse.setSuccessMessage("Camunda Deployment SUCCESS ["+orgFileName+"] : Deployment ID ["+d.getId()+"]");
log.info("Camunda Deployment SUCCESS [{}] : Deployment ID [{}]", orgFileName, d.getId());
}
catch (IOException e) {
camundaDeployResponse.setErrorMessage("Camunda Deployment FAILED : "+e.getMessage());
log.error("Camunda Deployment FAILED [{}]: {}", orgFileName, e.getMessage());
e.printStackTrace();
}
}
else {
camundaDeployResponse.setErrorMessage("Not a valid Camunda file (BPMN/DMN)");
log.error("Not a valid Camunda file (BPMN/DMN)");
}
return camundaDeployResponse;
}
}
I am looking to develop a Spring Cloud APIGW and Open API Specification Integration example. I am following: https://piotrminkowski.com/2018/04/26/quick-guide-to-microservices-with-spring-boot-2-0-eureka-and-spring-cloud/, but use all latest dependency of Spring Boot which is 2.7.0
http://localhost:8011/swagger-ui.html is not working
gateway.properties
server.port=8011
spring.application.name=api-gateway
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
spring.config.import=configserver:http://localhost:8012/
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
# Actuator
# https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.enabling
management.endpoint.gateway.enabled=true
management.endpoints.web.exposure.include=beans, health, metrics, mappings, env, info,configprops, caches, gateway
# All Microservices Routes
spring.cloud.gateway.routes[0].id=organization-service
spring.cloud.gateway.routes[0].uri=lb://organization-service
spring.cloud.gateway.routes[0].predicates[0]=Path=/organization/**
spring.cloud.gateway.routes[0].predicates[1]=Method=GET,POST,PUT,DELETE
spring.cloud.gateway.routes[1].id=department-service
spring.cloud.gateway.routes[1].uri=lb://department-service
spring.cloud.gateway.routes[1].predicates[0]=Path=/department/**
spring.cloud.gateway.routes[1].predicates[1]=Method=GET,POST,PUT,DELETE
spring.cloud.gateway.routes[2].id=employee-service
spring.cloud.gateway.routes[2].uri=lb://employee-service
spring.cloud.gateway.routes[2].predicates[0]=Path=/employee/**
spring.cloud.gateway.routes[2].predicates[1]=Method=GET,POST,PUT,DELETE
spring.cloud.gateway.routes[3].id=openapi
spring.cloud.gateway.routes[3].uri=http://localhost:${server.port}
spring.cloud.gateway.routes[3].predicates[0]=Path=/v3/api-docs/**
spring.cloud.gateway.routes[3].predicates[1]=Method=GET,POST,PUT,DELETE
spring.cloud.gateway.routes[4].id=openapi2
spring.cloud.gateway.routes[4].uri=http://localhost:${server.port}
spring.cloud.gateway.routes[4].predicates[0]=Path=/webjars/**
spring.cloud.gateway.routes[4].predicates[1]=Method=GET,POST,PUT,DELETE
# Open API Specification
springdoc.swagger-ui.urls[0].name=employee
springdoc.swagger-ui.urls[0].url=/v3/api-docs/employee
springdoc.swagger-ui.urls[1].name=department
springdoc.swagger-ui.urls[1].url=/v3/api-docs/department
springdoc.swagger-ui.urls[2].name=organization
springdoc.swagger-ui.urls[2].url=/v3/api-docs/organization
server.max-http-header-size=2000000
MainApp.java
package com.example.demo;
import java.util.ArrayList;
import java.util.List;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
#EnableEurekaClient
#SpringBootApplication
public class GatewayServiceApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServiceApplication.class, args);
}
#Autowired
RouteDefinitionLocator locator;
#Bean
public List<GroupedOpenApi> apis() {
List<GroupedOpenApi> groups = new ArrayList<>();
List<RouteDefinition> definitions = locator.getRouteDefinitions().collectList().block();
assert definitions != null;
definitions.stream().filter(routeDefinition -> routeDefinition.getId().matches(".*-service")).forEach(routeDefinition -> {
String name = routeDefinition.getId().replaceAll("-service", "");
groups.add(GroupedOpenApi.builder().pathsToMatch("/" + name + "/**").group(name).build());
});
return groups;
}
}
Note: I am showing for 1 microservice, I've done almost same for others
application.properties
server.port=${PORT:0}
spring.application.name=employee-service
spring.config.import=configserver:http://localhost:8012/
eureka.client.fetch-registry=true
eureka.client.register-with-eureka=true
springdoc.packages-to-scan=com.example.demo
and Added below to entry service main app.
MainApp.java
#OpenAPIDefinition(info = #Info(title = "Employee API", version = "1.0", description = "Documentation Employee API v1.0"))
#EnableEurekaClient
#SpringBootApplication
public class EmployeeServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EmployeeServiceApplication.class, args);
}
}
I have a Spring Cloud Gateway, a eureka service registry and two microservives. I have no problems with sending the request directely to the sevice with: http://localhost:8081/auth. When i want to use the Gateway http://localhost:8080/auth, i always get a 404 error response. The services and the gateway all connect to the eureka server.
Here is my code:
Gateway
application.properties:
server.port=8080
spring.application.name=gateway-service
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
spring.cloud.gateway.routes[0].id=auth-service
spring.cloud.gateway.routes[0].uri=lb://AUTH-SERVICE
spring.cloud.gateway.routes[0].predicates[0]=Path=/auth/**
spring.cloud.gateway.routes[1].id=songs-service
spring.cloud.gateway.routes[1].uri=lb://SONGS-SERVICE
spring.cloud.gateway.routes[1].predicates[0]=Path=/songs/**
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
Main:
package htw.kbe_beleg
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.cloud.netflix.eureka.EnableEurekaClient
#SpringBootApplication
#EnableEurekaClient
class GatewayApplication {
companion object {
#JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(GatewayApplication::class.java, *args)
}
}
}
Eureka
application properties:
server.port= 8761
spring.application.name=discovery-service
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
Main:
package htw.kbe_beleg
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer
#SpringBootApplication
#EnableEurekaServer
class EurekaServiceRegistration {
companion object {
#JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(EurekaServiceRegistration::class.java, *args)
}
}
}
Microservice
application.properties:
spring.datasource.url= jdbc:mysql://localhost:3306/authdb
spring.datasource.username= admin
server.port= 8081
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQLDialect
spring.jpa.properties.hibernate.show_sql= true
spring.jpa.hibernate.naming.implicit-strategy= org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy= org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.application.name= auth-service
eureka.client.service-url.defaultZone= http://localhost:8761/eureka/
Controller :
package htw.kbe_beleg.controller
import htw.kbe_beleg.model.Auth
import htw.kbe_beleg.repository.AuthRepository
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
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.RequestMapping
import org.springframework.web.bind.annotation.RestController
#RestController
class AuthController(val authRepository: AuthRepository) {
#PostMapping("/auth", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.TEXT_PLAIN_VALUE])
fun authenticateUser(#RequestBody authUser: Auth): ResponseEntity<String> {
...........
}
}
Main:
package htw.kbe_beleg
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.cloud.netflix.eureka.EnableEurekaClient
#SpringBootApplication
#EnableEurekaClient
class AuthMain {
companion object {
#JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(AuthMain::class.java, *args)
}
}
}
I already gone through nearly every related stackoverflow question, changed a lot with no result and reverted it. I just cant see what am i missing. Hope you can help me.
Shame on me. Problem was that i had the wrong dependencies for my gateway. I only had spring-cloud-starter-config, but i needed spring-cloud-starter-gateway... Took me over a week.
This is more of a question for a tool - googling around I haven't really had much luck.
So basically I have a standard spring boot app - and I have a unit test redis cache configuration. What I am looking to do is run the app context autowire some spring configs and test against a embedded redis cache if possible.
Closest I have come is this https://github.com/kstyrc/embedded-redis.
Problem with that is the lack of robust logging is making it difficult to run - its working locally, but when I push it up, Unix server build machine, its failing and no idea why.
If anyone has any idea of how to run integration tests this way - it would be great.
thanks,
Stefan
I am using embedded-redis for my integration testing with redisson java client.
Here is my dependency
compile group: 'org.redisson', name: 'redisson', version: '3.6.5'
testCompile group: 'it.ozimov', name: 'embedded-redis', version: '0.7.2'
Start embedded redis server before class and stop it in after class.
Redis property:
spring.redis.host=localhost
spring.redis.port=6379
Sample integration test.
import java.util.concurrent.TimeUnit;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit4.SpringRunner;
import redis.embedded.RedisServer;
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class RedisTest {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisTest.class);
private static RedisServer REDISSERVER = new RedisServer(6379);
#LocalServerPort
private int port;
#Autowired
private RedissonClient redissonClient;
#BeforeClass
public static final void before() {
REDISSERVER.start();
}
#AfterClass
public static final void after() {
REDISSERVER.stop();
}
#Test
public void testRedis() throws InterruptedException {
//map
RMap<String, String> map = redissonClient.getMap("user");
map.put("name", "Redis Server");
Assert.assertTrue(map.get("name").equals("Redis Server"));
//mapcache
RMapCache<String, String> mapCache = redissonClient.getMapCache("tempUser");
mapCache.put("name", "Redis Server", 5, TimeUnit.SECONDS);
Assert.assertTrue(mapCache.get("name").equals("Redis Server"));
Thread.sleep(7000); //wait for 7 sec.
Assert.assertTrue(mapCache.get("name") == null);
}
}
I am new to Hystrix Dashboard. I have written sample application with Hystrix.
I want to see the Hystrix chart (command metric stream). But I am getting the below error:
Circuit: Unable to connect to Command Metric Stream
Thread Pools: Loading...
I am using STS with Maven.
Below is the code used:
Simple server microservice application (Spring boot web running in port 8085)
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
#RestController
#SpringBootApplication
public class BookstoreApplication {
#RequestMapping(value = "/recommended")
public String readingList(){
return "Spring in Action (Manning), Cloud Native Java (O'Reilly), Learning Spring Boot (Packt)";
}
public static void main(String[] args) {
SpringApplication.run(BookstoreApplication.class, args);
}
}
Simple client microservice application (Spring boot web running in port 8095) I have included the dependency of Hystrix and Hystrix Dashboard along with Web, so all the Hystrix dependencies are in classpath
package hello;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
#Service
public class BookService {
private final RestTemplate restTemplate;
public BookService(RestTemplate rest) {
this.restTemplate = rest;
}
#HystrixCommand(fallbackMethod = "reliable")
public String readingList() {
URI uri = URI.create("http://localhost:8090/recommended");
return this.restTemplate.getForObject(uri, String.class);
}
public String reliable() {
return "Cloud Native Java (O'Reilly)";
}
}
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.web.client.RestTemplate;
#EnableHystrixDashboard
#EnableHystrix
#EnableCircuitBreaker
#RestController
#SpringBootApplication
public class ReadingApplication {
#Autowired
private BookService bookService;
#Bean
public RestTemplate rest(RestTemplateBuilder builder) {
return builder.build();
}
#RequestMapping("/to-read")
public String toRead() {
return bookService.readingList();
}
public static void main(String[] args) {
SpringApplication.run(ReadingApplication.class, args);
}
}
By running the above code, the hystrix is working fine, when the BooKStoreApplication is down, it is going to fallback method.
Both the urls are working fine.
Normal Case:
http://localhost:8085/recommended
Output: Spring in Action (Manning), Cloud Native Java (O'Reilly), Learning Spring Boot (Packt)
http://localhost:8095/to-read
Output: Spring in Action (Manning), Cloud Native Java (O'Reilly), Learning Spring Boot (Packt)
When BookStoreApplication is down (http://localhost:8085/recommended) accessing http://localhost:8095/to-read returns "Cloud Native Java (O'Reilly)" as expected.
But when I tried to invoke this url http://localhost:8095/hystrix, I am getting the Hystrix DashBoard Page and asking for the stream value.
I have tried given http://localhost:8095/ or http://localhost:8095/to-read, and clicked "Monitor Stream" and it is going to next page with error:
Circuit: Unable to connect to Command Metric Stream
Thread Pools: Loading...
I've experienced the same. The main problem was, that I didn't have the actuator dependency in my maven pom. So I could not get the hystrix stream.
Include the spring-boot-actuator.
Check if localhost:8085/health is running.
Try to enter localhost:8085/hystrix.stream to stream value in Hystrix Dashboard.
Execute the service few times -> the dashboard should show the monitored method/command.