generator-jhipster icon indicating copy to clipboard operation
generator-jhipster copied to clipboard

Add support for Spring Cloud Gateway MVC

Open mraible opened this issue 1 year ago • 17 comments

Overview of the feature request

When we first integrated Spring Cloud Gateway, it only supported WebFlux. Now it supports Spring MVC, so we should try to support it too.

https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway-server-mvc.html

Motivation for or Use Case

If you're developing microservices with Spring MVC, you might prefer MVC in your gateway too.

Related issues or PR
  • [x] Checking this box is mandatory (this is just to show you read everything)

mraible avatar Apr 02 '24 15:04 mraible

I will take a look.

mshima avatar Apr 02 '24 15:04 mshima

There is no support for service discovery yet.

mshima avatar Apr 04 '24 13:04 mshima

I was able to get service discovery working when I wrote this blog post back in December.

mraible avatar Apr 04 '24 14:04 mraible

I was able to get service discovery working when I wrote this blog post back in December.

There is no support for spring.cloud.gateway.discovery.locator.enabled=true So this does not work: https://github.com/jhipster/generator-jhipster/blob/1d0a1c2b47499ecb43b294e87a531a982e7776e4/generators/server/templates/src/main/resources/config/application.yml.ejs#L194-L206

Routes needs to be manually configured.

mshima avatar Apr 04 '24 15:04 mshima

Confirmed: https://github.com/spring-cloud/spring-cloud-gateway/issues/3332

mraible avatar Apr 04 '24 15:04 mraible

Gateway is reactive by default it’s possible to force it imperative by setting reactive false or --no-reactive.

Issues:

  • openapi generated server url is wrong in microservices.
  • JwtTokenRelay needs to be implemented.

Maybe we should require --experimental flag.

mshima avatar Apr 05 '24 15:04 mshima

Yes, we should add the --experimental flag.

DanielFran avatar Apr 05 '24 15:04 DanielFran

Yes, we should add the --experimental flag.

Done

mshima avatar Apr 06 '24 13:04 mshima

I tried using the following JDL with 8.3.0:

application {
  config {
    baseName gateway
    packageName org.jhipster.gateway
    applicationType gateway
    authenticationType oauth2
    buildTool gradle
    clientFramework react
    prodDatabaseType postgresql
    serviceDiscoveryType consul
    testFrameworks [cypress]
    microfrontends [blog, store]
  }
}

application {
  config {
    baseName blog
    packageName org.jhipster.blog
    applicationType microservice
    authenticationType oauth2
    buildTool gradle
    clientFramework react
    databaseType neo4j
    enableHibernateCache false
    serverPort 8081
    serviceDiscoveryType consul
    testFrameworks [cypress]
  }
  entities Blog, Post, Tag
}

application {
  config {
    baseName store
    packageName org.jhipster.store
    applicationType microservice
    authenticationType oauth2
    buildTool gradle
    clientFramework react
    databaseType mongodb
    enableHibernateCache false
    serverPort 8082
    serviceDiscoveryType consul
    testFrameworks [cypress]
  }
  entities Product
}

entity Blog {
  name String required minlength(3)
  handle String required minlength(2)
}

entity Post {
  title String required
  content TextBlob required
  date Instant required
}

entity Tag {
  name String required minlength(2)
}

entity Product {
  title String required
  price BigDecimal required min(0)
  image ImageBlob
}

relationship ManyToOne {
  Blog{user(login)} to User with builtInEntity
  Post{blog(name)} to Blog
}

relationship ManyToMany {
  Post{tag(name)} to Tag{post}
}

paginate Post, Tag with infinite-scroll
paginate Product with pagination

deployment {
  deploymentType docker-compose
  serviceDiscoveryType consul
  appsFolders [gateway, blog, store]
  dockerRepositoryName "mraible"
}

deployment {
  deploymentType kubernetes
  appsFolders [gateway, blog, store]
  clusteredDbApps [store]
  kubernetesNamespace demo
  kubernetesUseDynamicStorage true
  kubernetesStorageClassName ""
  serviceDiscoveryType consul
  dockerRepositoryName "mraible"
}

The command I used to create my apps:

jhipster --experimental jdl app.jdl

If I look for webflux in the gateway's build file, it's still there:

$ cat gateway/build.gradle | grep webflux
    implementation "org.springframework.boot:spring-boot-starter-webflux"
    implementation libs.springdoc.openapi.starter.webflux.api

Do I have to specify reactive false for the gateway?

mraible avatar Apr 09 '24 22:04 mraible

Do I have to specify reactive false for the gateway?

Yes

mshima avatar Apr 09 '24 23:04 mshima

I used the following JDL to create a set of apps:

application {
  config {
    baseName gateway
    reactive false
    packageName org.jhipster.gateway
    applicationType gateway
    authenticationType oauth2
    buildTool gradle
    clientFramework react
    prodDatabaseType postgresql
    serviceDiscoveryType consul
    testFrameworks [cypress]
    microfrontends [blog, store]
  }
}

application {
  config {
    baseName blog
    reactive false
    packageName org.jhipster.blog
    applicationType microservice
    authenticationType oauth2
    buildTool gradle
    clientFramework react
    databaseType neo4j
    enableHibernateCache false
    serverPort 8081
    serviceDiscoveryType consul
    testFrameworks [cypress]
  }
  entities Blog, Post, Tag
}

application {
  config {
    baseName store
    reactive false
    packageName org.jhipster.store
    applicationType microservice
    authenticationType oauth2
    buildTool gradle
    clientFramework react
    databaseType mongodb
    enableHibernateCache false
    serverPort 8082
    serviceDiscoveryType consul
    testFrameworks [cypress]
  }
  entities Product
}

entity Blog {
  name String required minlength(3)
  handle String required minlength(2)
}

entity Post {
  title String required
  content TextBlob required
  date Instant required
}

entity Tag {
  name String required minlength(2)
}

entity Product {
  title String required
  price BigDecimal required min(0)
  image ImageBlob
}

relationship ManyToOne {
  Blog{user(login)} to User with builtInEntity
  Post{blog(name)} to Blog
}

relationship ManyToMany {
  Post{tag(name)} to Tag{post}
}

paginate Post, Tag with infinite-scroll
paginate Product with pagination

deployment {
  deploymentType docker-compose
  serviceDiscoveryType consul
  appsFolders [gateway, blog, store]
  dockerRepositoryName "mraible"
}

deployment {
  deploymentType kubernetes
  appsFolders [gateway, blog, store]
  clusteredDbApps [store]
  kubernetesNamespace demo
  kubernetesUseDynamicStorage true
  kubernetesStorageClassName ""
  serviceDiscoveryType consul
  dockerRepositoryName "mraible"
}

I used the following command:

jhipster jdl reactive-mf.jdl --monorepository --workspaces --experimental

When I start everything, the gateway is not able to load the microservice apps and the menu says "Error loading component".

Screenshot 2024-04-10 at 10 09 19 AM

The logs show a 404 when trying to connect to downstream services.

2024-04-10T10:23:02.247-04:00  WARN 28572 --- [  XNIO-1 task-4] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.servlet.resource.NoResourceFoundException: No static resource services/store/remoteEntry.js.]
2024-04-10T10:23:02.248-04:00  WARN 28572 --- [  XNIO-1 task-7] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.servlet.resource.NoResourceFoundException: No static resource services/blog/remoteEntry.js.]

Everything works as expected when I change to use reactive true in the JDL.

mraible avatar Apr 10 '24 14:04 mraible

The logs show a 404 when trying to connect to downstream services.

Since there is no service discovery integration, due to https://github.com/jhipster/generator-jhipster/issues/25715#issuecomment-2037578525, that's expected. Routes needs to be manually configured.

mshima avatar Apr 10 '24 15:04 mshima

@mraible following https://github.com/jhipster/generator-jhipster/pull/25817 the routes option accepts "route", "route:host" or "route:host:port". Updated jdl with routes option:

application {
  config {
    baseName gateway
    reactive false
    packageName org.jhipster.gateway
    applicationType gateway
    authenticationType oauth2
    buildTool gradle
    clientFramework react
    prodDatabaseType postgresql
    serviceDiscoveryType consul
    testFrameworks [cypress]
    microfrontends [blog, store]
    routes ["blog:blog:8081", "store:store:8082"]
  }
}

application {
  config {
    baseName blog
    reactive false
    packageName org.jhipster.blog
    applicationType microservice
    authenticationType oauth2
    buildTool gradle
    clientFramework react
    databaseType neo4j
    enableHibernateCache false
    serverPort 8081
    serviceDiscoveryType consul
    testFrameworks [cypress]
  }
  entities Blog, Post, Tag
}

application {
  config {
    baseName store
    reactive false
    packageName org.jhipster.store
    applicationType microservice
    authenticationType oauth2
    buildTool gradle
    clientFramework react
    databaseType mongodb
    enableHibernateCache false
    serverPort 8082
    serviceDiscoveryType consul
    testFrameworks [cypress]
  }
  entities Product
}

entity Blog {
  name String required minlength(3)
  handle String required minlength(2)
}

entity Post {
  title String required
  content TextBlob required
  date Instant required
}

entity Tag {
  name String required minlength(2)
}

entity Product {
  title String required
  price BigDecimal required min(0)
  image ImageBlob
}

relationship ManyToOne {
  Blog{user(login)} to User with builtInEntity
  Post{blog(name)} to Blog
}

relationship ManyToMany {
  Post{tag(name)} to Tag{post}
}

paginate Post, Tag with infinite-scroll
paginate Product with pagination

deployment {
  deploymentType docker-compose
  serviceDiscoveryType consul
  appsFolders [gateway, blog, store]
  dockerRepositoryName "mraible"
}

deployment {
  deploymentType kubernetes
  appsFolders [gateway, blog, store]
  clusteredDbApps [store]
  kubernetesNamespace demo
  kubernetesUseDynamicStorage true
  kubernetesStorageClassName ""
  serviceDiscoveryType consul
  dockerRepositoryName "mraible"
}

mshima avatar Apr 11 '24 21:04 mshima

@mshima This seems like quite a few changes for a feature that might go away once Spring Cloud Gateway MVC supports it. However, I talked with the Gateway project lead and he said service discovery won't be GA until November, so we should probably implement this workaround until then.

mraible avatar Apr 25 '24 18:04 mraible

This could be reused for https://github.com/jhipster/generator-jhipster/issues/21012, otherwise we should just close it.

mshima avatar Apr 25 '24 18:04 mshima

@egvimo Would this solution work to solve #21012?

mraible avatar Apr 25 '24 18:04 mraible

Yes, this would work, I think.

egvimo avatar Apr 25 '24 19:04 egvimo

Upstream issues:

  • https://github.com/spring-cloud/spring-cloud-gateway/issues/3332: support manually added routes.
  • https://github.com/spring-cloud/spring-cloud-gateway/issues/3382: duplicate required filters in every route
  • https://github.com/spring-cloud/spring-cloud-gateway/issues/3354: manually set X-Forwarded-Prefix.
  • https://github.com/spring-cloud/spring-cloud-gateway/issues/3233: retrieve routes based on https://github.com/spring-cloud/spring-cloud-gateway/issues/3233#issuecomment-1989318818

mshima avatar Jul 15 '24 21:07 mshima