Intesys has been using JHipster for more than two years as the core component of its API-First enterprise backend services. We will show you how to combine the benefits of the JHipster CRUD stack along with the OpenAPI-generated endpoints, seamlessy integrating JHipster with custom single page applications, mobile apps and legacy microservices. We will also share some best practices regarding the evolution of JHipster projects used in production, how to find the extension points and how to take advantage of modules and blueprints.
Speech by Enrico Costanti @JHipster Conf' 2019, Paris
1. Jhipster Beyond CRUD
API First for Enterprise
2 7 / 0 6 / 2 0 1 9 - I N T E S Y S
This material has been prepared by personnel of Intesys S.r.l. and is provided on a confidential basis.
All the materials, animations, graphics and concepts may not be used, reproduced, redistributed or transmitted, in whole or in part, without prior written permission from the owner. Any unauthorized use is strictly prohibited.
2. W H O A R E W E ?
Who we are
Paolo Quaglia
senior project manager
and software architect - @p_quail
Enrico Costanzi
senior software developer - @enricocostanzi
3. W H O A R E W E ?
Italian 100 employees company
Enterprise industry: Banks, insurance companies,
manufacturing
Our Development Pattern: Design to Deliver
4. W H O A R E W E ?
• We started with Jhipster in 2017
• 3rd Jhipster Bronze Sponsor
• ~10 developers on average working with Jhipster
• 5 projects running in production
• 3 big project in Development phase
• 2 Jhipster modules published
• More modules/blueprints – work in progress
• Some contributions to the main generator
Intesys & Jhipster
7. I N T E S Y S & J H I P S T E R
2017 • Angular Monolith (Single Node)
• Jhipster 4.10.1
• 12 entities
• ~200 users
• No swagger endpoints
• 3 Roles
• No native queries
• Local Cache
• 2 dev teams involved
8. I N T E S Y S & J H I P S T E R
2019 • React Monolith (multiple nodes)
• Jhipster 5.8.2
• 120 entities (2 JDL files)
• ~4000 users
• 100 OpenAPI endpoints
• 7 roles
• Many native queries
• Distributed cache
• 5 dev teams involved
13. B E Y O N D C R U D
UserJWTController
AccountResource
UserResource
Entity1Resource Entity1Service Entity1Repository
Entity1ResourceExt Entity1ServiceExt Entity1RepoExt
CustomController1
UserService UserRepository
CustomService1
CustomController2 CustomService2
UserResourceExt UserServiceExt
17. C O D E F I R S T J H I P S T E R
UserJWTController
AccountResource
UserResource
Entity1Resource
Entity1ResourceExt
CustomController1
CustomController2
UserResourceExt
Swagger UI & Springfox
/v2/api-docs
19. C O D E F I R S T J H I P S T E R
Split your Swagger specs
/v2/api-docs?group=auth
/v2/api-docs?group=admin
/v2/api-docs
UserJWTController
AccountResource
UserResource
Entity1Resource
Entity1ResourceExt
CustomController1
CustomController2
UserResourceExt
20. C O D E F I R S T J H I P S T E R
Split your Swagger specs
/v2/api-docs?group=auth
/v2/api-docs?group=admin
/v2/api-docs
UserJWTController
AccountResource
UserResource
Entity1Resource
Entity1ResourceExt
CustomController1
CustomController2
UserResourceExt
@Bean
public Docket authDocket() {
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.groupName("auth")
.forCodeGeneration(true)
.directModelSubstitute(java.nio.ByteBuffer.class, String.class)
.directModelSubstitute(URI.class, String.class)
.directModelSubstitute(StatusType.class, Integer.class)
.genericModelSubstitutes(ResponseEntity.class)
.additionalModels(typeResolver.resolve(Problem.class))
.select()
.paths(Predicates.or(
regex("/api/account.*"), regex("/api/authenticate.*"),
regex("/api/activate.*"), regex("/api/register.*"))
)
.build();
return docket;
}
21. C O D E F I R S T J H I P S T E R
Export your Swagger specs
UserJWTController
AccountResource
UserResource
Entity1Resource
Entity1ResourceExt
CustomController1
CustomController2
UserResourceExt
account-spec.json
default-spec.json
account-spec.json
22. C O D E F I R S T J H I P S T E R
Export your Swagger specs
UserJWTController
AccountResource
UserResource
Entity1Resource
Entity1ResourceExt
CustomController1
CustomController2
UserResourceExt
account-spec.json
default-spec.json
account-spec.json
@SpringBootTest(classes = JhipetstoreApp.class)
@ActiveProfiles(value = {JHipsterConstants.SPRING_PROFILE_SWAGGER})
public class OpenApiSpecGeneratorIT {
@Autowired DocumentationCache documentationCache;
@Autowired ServiceModelToSwagger2Mapper mapper;
@Autowired JsonSerializer jsonSerializer;
@Test
void createOpenApiJsonSpec() throws IOException {
for (Map.Entry<String, Documentation> documentationEntry : documentationCache.all().entrySet()) {
String groupName = documentationEntry.getKey();
Documentation documentation = documentationEntry.getValue();
Swagger swagger = mapper.mapDocumentation(documentation);
Json spec = jsonSerializer.toJson(swagger);
File specsDir = new File("target/swagger-specs");
if (!specsDir.exists()) specsDir.mkdirs();
IOUtils.write(spec.value().getBytes(), new FileOutputStream("target/swagger-specs/”
+ groupName + «-spec.json"));
}
}
}
23. C O D E F I R S T - C O N T R A C T D R I V E N C O M M U N I C A T I O N
UserJWTController
AccountResource
UserResource
Entity1Resource Entity1Service Entity1Repository
Entity1ResourceExt Entity1ServiceExt Entity1RepoExt
CustomController1
UserService UserRepository
CustomService1
CustomController2 CustomService2
UserResourceExt UserServiceExt
24. B E Y O N D C R U D
Code First
• API Consumers must wait for specs to be
exposed or exported
• Only Swagger v2 support (OpenAPI 3
generation is WIP)
• Springfox scanning is slow with lots of
controllers
• Security rules must be coded in dockets
26. A P I F I R S T J H I P S T E R
API First
• Design
• API Design before writing code
• Interoperability
• Helps detecting breaking changes
• Developer Experience
• Decoupling client/server development
• Developers contribute to API definition
• Approval process during API design
• User Experience
• API design with UX/UI in mind
27. 27
A P I F I R S T J H I P S T E R
Server side code generation from swagger / openapi specifications
ReadPetsApi
ReadPetApiDelegate
API First Jhipster
src/main/resources/swagger/api.yml
ReadPetsApiDelegateImpl
ReadPetsApiController
28. A P I F I R S T
modelNameSuffix: avoid DTOS
with the same class name:
• PetDTO (Jhipster)
• PetApiDto (OpenAPI DTO)
useTags: Groups the OpenAPI
endpoints by tag (not by path
prefix)
OpenAPI Generator Configuration
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>${openapi-generator-maven-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>…/resources/swagger/api.yml</inputSpec>
<generatorName>spring</generatorName>
…
<modelNameSuffix>ApiDTO</modelNameSuffix>
<configOptions>
<delegatePattern>true</delegatePattern>
<title>jhipetstore</title>
<useTags>true</useTags>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
29. C O D E F I R S T A N D A P I F I R S T
What changed?
UserJWTController
AccountResource
UserResource
Entity1Resource
Entity1ResourceExt
CustomController1
CustomController2
UserResourceExt
OpenAPIController1
OpenAPIController2
UserJWTController
AccountResource
UserResource
Entity1Resource
Entity1ResourceExt
UserResourceExt
30. C O D E F I R S T A N D A P I F I R S T
UserJWTController
AccountResource
UserResource
Entity1Resource Entity1Service Entity1Repository
Entity1ResourceExt Entity1ServiceExt Entity1RepoExt
UserService UserRepository
CustomAPIService1
UserResourceExt UserServiceExt
OpenAPIController1
OpenAPIController2 CustomAPIService2
JDL
Code First
API First
31. C O D E F I R S T A N D A P I F I R S T
Jhipster Frontend
OpenAPIController1
OpenAPIController2
UserJWTController
AccountResource
UserResource
Entity1Resource
Entity1ResourceExt
UserResourceExt
JDL APIs:
• Jhipster CRUD
• Jhipster Administration
• Customizations
API First Endpoints
(generated client SDK):
• Custom Pages
• Design First
32. C O D E F I R S T A N D A P I F I R S T
External Apps – API + Code First
OpenAPIController1
OpenAPIController2
UserJWTController
AccountResource
UserResource
Entity1Resource
Entity1ResourceExt
UserResourceExt
External Backend Applications
Mobile Apps
Custom Single Page Applications
33. C O D E F I R S T A N D A P I F I R S T
External Apps – API First only
OpenAPIController1
OpenAPIController2
UserJWTController
AccountResource
UserResource
Entity1Resource
Entity1ResourceExt
UserResourceExt
External Backend Applications
Mobile Apps
Custom Single Page Applications
34. A P I F I R S T
• Code duplication
• Jhipster DTOs ~= OpenAPI DTOs
• Basic CRUD Operations have to be rewritten in the spec
• Metamodel Filtering and pagination (coded in the spec)
• Authentication (duplicated endpoints in some cases)
API First Limitations
36. D E M O
Customizing Using Modules
OpenAPIController1
OpenAPIController2
OpenAPIController1
OpenAPIController2
Generated by OpenAPI Generator
Generated by
Springfox
Designed specs
Designed specs
• api.yml served statically (not generated by Springfox)
by a dedicated endpoint
• Replaces Swagger UI 2 with Swagger UI 3 (iframe)
• API version tag published in consul or Jhipster registry
37. D E M O
• Jhipster submodule to generate client code from swagger/openapi
specifications
• Integration of generator-jhipster-swagger-cli in the main generator
• Wraps the official npm module openapi-generator-cli
• Supports backend client generation (Feign clients)
• Future steps: generate client code for Angular / React
Jhipster OpenAPI Client (Preview)
$ jhipster openapi-client
39. T E S T C O N T A I N E R S
Testcontainers
“Testcontainers is a Java library that
supports JUnit tests, providing
lightweight, throwaway instances of
common databases, Selenium web
browsers, or anything else that can run
in a Docker container”
Jhipster Integration Tests
• Native SQL Queries & Functions
• Liquibase changelogs
40. T E S T C O N T A I N E R S
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.11.3</version>
<scope>test</scope>
</dependency>
The easy way
jdbc:tc:postgresql:11.3://localhost:5432/jhipetstore
org.testcontainers.jdbc.ContainerDatabaseDriver
1. add dependency
2. change test properties
3. ./mvnw verify
4. DONE! ☺
41. T E S T C O N T A I N E R S
The custom way
jdbc:tc:postgresql:11.3://localhost:5432/jhipetstore
org.testcontainers.jdbc.ContainerDatabaseDriver
@TestConfiguration
@Profile("testcontainers")
public class IntegrationTestsConfiguration {
@Bean
public DataSource dataSource() {
dbContainer =
new MSSQLServerContainer("my-docker-account/mssql-server-custom:latest")
.withPassword("yourStrong(!)Password");
dbContainer.start(); //starts the container
String jdbcUrl = dbContainer.getJdbcUrl();
logger.info("Database started, creating datasource for url: '{}'", jdbcUrl);
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(jdbcUrl);
dataSource.setUsername(dbContainer.getUsername());
dataSource.setPassword(dbContainer.getPassword());
dataSource.setDriverClassName(dbContainer.getDriverClassName());
dataSource.setAutoCommit(false);
return dataSource;
}
generator-jhipster-testcontainers
42. T E S T C O N T A I N E R S
The custom way
jdbc:tc:postgresql:11.3://localhost:5432/jhipetstore
org.testcontainers.jdbc.ContainerDatabaseDriver
@TestConfiguration
@Profile("testcontainers")
public class IntegrationTestsConfiguration {
@Bean
public DataSource dataSource() {
dbContainer =
new MSSQLServerContainer("my-docker-account/mssql-server-custom:latest")
.withPassword("yourStrong(!)Password");
dbContainer.start(); //starts the container
String jdbcUrl = dbContainer.getJdbcUrl();
logger.info("Database started, creating datasource for url: '{}'", jdbcUrl);
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(jdbcUrl);
dataSource.setUsername(dbContainer.getUsername());
dataSource.setPassword(dbContainer.getPassword());
dataSource.setDriverClassName(dbContainer.getDriverClassName());
dataSource.setAutoCommit(false);
return dataSource;
}
generator-jhipster-testcontainers
$ yo jhipster-testcontainers
$ ./mvnw verify -Dspring.profiles.active=testcontainers
44. A P I F I R S T
• CRUD stack is the foundation of your APP
• How CRUD and OpenAPI layers coexist
• Go API First only for mission critical APIs
• Use Testcontainers
• Share your modules ☺
Recap
45. A P I F I R S T
• Jhipster Conf 2018
• Connect your JHipster apps to APIs with Swagger and gRPC by Christophe Bornet
(https://www.youtube.com/watch?v=XWa-53-mDwY)
• Custom and Generated Code Side by Side by Antonio Goncalves
(https://www.youtube.com/watch?v=9WVpwIUEty0)
• generator-jhipster-apiutils module
• https://www.npmjs.com/package/generator-jhipster-apiutils
• Testcontainers & Jhipster
• https://www.youtube.com/watch?time_continue=3&v=L_i61qTg510
• https://atomfrede.gitlab.io/2019/05/jhipster-with-testcontainers/
• https://www.npmjs.com/package/generator-jhipster-testcontainers
• https://github.com/danielgtaylor/apisprout
• Demo Code Samples:
• https://github.com/intesys/jhipsterconf2019-petstore-demo
• https://github.com/intesys/jhipsterconf2019-petstore-client
References
46. Intesys S.r.l.
Via Roveggia, 122/A - 37136 Verona VR
T. +39 045 503 663
F. +39 045 503 604
@ info@intesys.it
intesys.it
Enrico Costanzi
S e n i o r s o f t w a r e d e v e l o p e r
enrico.costanzi@intesys.it
@enricocostanzi
Paolo Quaglia
S e n i o r P r o j e c t M a n a g e r a n d A r c h i t e c t
paolo.quaglia@intesys.it
@p_quail