Technorage

Where I rage about technology and stuff!

Deepu K Sasidharan
Deepu K Sasidharan JHipster co-lead, Java, JS, Cloud Native Advocate, Dev @ XebiaLabs, Author, Speaker, Software craftsman. Loves simple & beautiful code. bit.ly/JHIPSTER-BOOK | 17 mins read

Create full Microservice stack using JHipster Domain Language under 30 minutes

Create full Microservice stack using JHipster Domain Language under 30 minutes

Part of "Microservices with JHipster" series

It’s been quite a while since I wrote a blog, I did a few some years ago but never really continued writing. So when I decided to start writing again, I didn’t have to think a lot about a topic as it was very obvious — JHipster.

JHipster is a development platform for Java web applications and microservices development. If you are a JVM developer you might have already heard about JHipster. If not, well, you are missing out on a lot and I highly recommend you check it out. You can also check out my book “Full Stack Development with JHipster” on Amazon and Packt to learn about JHipster.

I have been working on JHipster from April 2015 and the coolest feature that I got to implement so far is definitely multiple applications generation using JDL. This feature is available in the latest version of JHipster. If you are not familiar with JDL, I recommend you to check out the docs at https://www.jhipster.tech/jdl/

The E-Commerce application

So let us see how we can create a microservice stack using JHipster. We will build an e-commerce store today. The stack includes-

  • Service discovery using JHipster Registry, a Spring boot application that packs Eureka server and Spring cloud config server.

  • API management and Gateway using Spring Boot, Netflix Zuul, ReactJS, and Swagger.

  • Microservices using Spring Boot.

  • Monitoring using JHipster Console which is made of the Elastic stack(ELK) and Zipkin.

Microservice application architectureMicroservice application architecture

The Gateway routes incoming requests to two microservices, Invoice application, and Notification application.

Requirements

In order to follow this tutorial, you would need a recent version of Docker, Docker-compose, NodeJS and Java installed on your computer. The below are the versions I have installed(Update: With JHipster 6+ you can use Java 11 & 12).

1
2
3
4
5
6
7
8
9
10
11
12
13
$ docker -v                                                                                                                       
Docker version 18.06.1-ce, build e68fc7a

$ docker-compose -v                                
docker-compose version 1.20.1, build 5d8c71b

$ node -v                
v8.11.4

$ java -version          
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (Zulu 8.38.0.13-CA-linux64) (build 1.8.0_212-b04)
OpenJDK 64-Bit Server VM (Zulu 8.38.0.13-CA-linux64) (build 25.212-b04, mixed mode)

First, install the latest version of JHipster

1
$ npm install generator-jhipster -g

Verify that you have version 5.3.4 or above by running

1
$ jhipster --version

Creating the JDL

Now let us create our JDL. Head over to the JDL Studio or your favorite IDE/Editor(You can use JHipster IDE plugin if you like).

First, let us define our applications. We will start with the Gateway

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
application {
  config {
    baseName store,
    applicationType gateway,
    packageName com.jhipster.demo.store,
    serviceDiscoveryType eureka,
    authenticationType jwt,
    prodDatabaseType mysql,
    cacheProvider hazelcast,
    buildTool gradle,
    clientFramework react,
    testFrameworks [protractor]
  }
  entities *
}

Most of the options are self-explanatory, we are building an application named Store of type Gateway with JWT authentication and Eureka-based service discovery. The application uses a MySQL database and Hazelcast for the cache. It’s built using Gradle. For the client-side, it uses React and Sass. It also has Protractor for end-to-end testing.

At the end of the definition you can see entities *, we will come to this later.

Now let us define our Invoice microservice

1
2
3
4
5
6
7
8
9
10
11
12
13
application {
  config {
    baseName invoice,
    applicationType microservice,
    packageName com.jhipster.demo.invoice,
    serviceDiscoveryType eureka,
    authenticationType jwt,
    prodDatabaseType mysql,
    buildTool gradle,
    serverPort 8081
  }
  entities Invoice, Shipment
}

It follows similar options like our Gateway and since it is microservice it doesn’t define any client-side options and also skips user management as it will be handled by the Gateway. Additionally, we have also mentioned a custom port 8081 since we do not want this application to conflict with the default port 8080 used by the Gateway.

Now let us define the second microservice, the Notification application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
application {
  config {
    baseName notification,
    applicationType microservice,
    packageName com.jhipster.demo.notification,
    serviceDiscoveryType eureka,
    authenticationType jwt,
    databaseType mongodb,
    cacheProvider no,
    enableHibernateCache false,
    buildTool gradle,
    serverPort 8082
  }
  entities Notification
}

This application follows many options similar to the Gateway and Invoice application but instead of using MySQL it uses MongoDB as its database and also disables cache.

Now that our application definitions are done, we will proceed to define our entity model.

For our Gateway store application, let us define the below entities and enums

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/** Product sold by the Online store */
entity Product {
    name String required
    description String
    price BigDecimal required min(0)
    size Size required
    image ImageBlob
}

enum Size {
    S, M, L, XL, XXL
}

entity ProductCategory {
    name String required
    description String
}

entity Customer {
    firstName String required
    lastName String required
    gender Gender required
    email String required pattern(/^[^@\s]+@[^@\s]+\.[^@\s]+$/)
    phone String required
    addressLine1 String required
    addressLine2 String
    city String required
    country String required
}

enum Gender {
    MALE, FEMALE, OTHER
}

entity ProductOrder {
    placedDate Instant required
    status OrderStatus required
    code String required
    invoiceId Long
}

enum OrderStatus {
    COMPLETED, PENDING, CANCELLED
}

entity OrderItem {
    quantity Integer required min(0)
    totalPrice BigDecimal required min(0)
    status OrderItemStatus required
}

enum OrderItemStatus {
    AVAILABLE, OUT_OF_STOCK, BACK_ORDER
}

relationship OneToOne {
    Customer{user(login) required} to User
}

relationship ManyToOne {
    OrderItem{product(name) required} to Product
}

relationship OneToMany {
    Customer{order} to ProductOrder{customer(email) required},
    ProductOrder{orderItem} to OrderItem{order(code) required},
    ProductCategory{product} to Product{productCategory(name)}
}

service Product, ProductCategory, Customer, ProductOrder, OrderItem with serviceClass
paginate Product, Customer, ProductOrder, OrderItem with pagination

The JDL defines the entities, enums, the relationship between entities and options like pagination and service layer.

The entity field definition follows the syntax

1
2
3
entity <entity name> {
  <field name> <type> [<validation>*]
}

The relationship definition follows the syntax

1
2
3
4
5
relationship (OneToMany | ManyToOne | OneToOne | ManyToMany) {
    <from entity>[{<relationship name>[(<display field>)]}] 
    to 
    <to entity>[{<relationship name>[(<display field>)]}]
}

Refer the JDL docs for full DSL reference.

The Invoice microservice application has the following entities

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
entity Invoice {
    code String required
    date Instant required
    details String
    status InvoiceStatus required
    paymentMethod PaymentMethod required
    paymentDate Instant required
    paymentAmount BigDecimal required
}

enum InvoiceStatus {
    PAID, ISSUED, CANCELLED
}

entity Shipment {
    trackingCode String
    date Instant required
    details String
}

enum PaymentMethod {
    CREDIT_CARD, CASH_ON_DELIVERY, PAYPAL
}

relationship OneToMany {
    Invoice{shipment} to Shipment{invoice(code) required}
}

service Invoice, Shipment with serviceClass
paginate Invoice, Shipment with pagination
microservice Invoice, Shipment with invoice

Pay attention to the last microservice option declared here, it specifies that these entities belong to the microservice named invoice so that our Gateway knows where to route requests for these entities.

Now let us see the entities for the Notification microservice application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
entity Notification {
    date Instant required
    details String
    sentDate Instant required
    format NotificationType required
    userId Long required
    productId Long required
}

enum NotificationType {
    EMAIL, SMS, PARCEL
}

microservice Notification with notification

Now let us go back to the entities keyword we used in our application definitions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
application {
  config {
    ...
  }
  entities *
}

application {
  config {
    ...
  }
  entities Invoice, Shipment
}

application {
  config {
    ...
  }
  entities Notification
}

/* Entities for Store Gateway */

entity Product {
    ...
}

entity ProductCategory {
    ...
}

entity Customer {
    ...
}

entity ProductOrder {
    ...
}

entity OrderItem {
    ...
}

microservice Invoice, Shipment with invoice

/* Entities for Invoice microservice */
entity Invoice {
    ...
}

entity Shipment {
    ...
}

/* Entities for notification microservice */

entity Notification {
    ...
}

microservice Notification with notification

Here we instruct the store gateway application that it should contain all the entities defined in the JDL and the gateway will know to skip server-side code for the entities that belong to another microservice and hence will only generate the client-side code for those, here namely Invoice, Shipment, and Notification. We also instruct the Invoice application and Notification application to include its entities.

Generating the applications

Create a folder where we want to create our microservice stack.

1
$ mkdir ecommerce && cd ecommerce

Now, let us put everything together into a JDL file. Let us call it app.jdl and save it into this folder.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
application {
  config {
    baseName store,
    applicationType gateway,
    packageName com.jhipster.demo.store,
    serviceDiscoveryType eureka,
    authenticationType jwt,
    prodDatabaseType mysql,
    cacheProvider hazelcast,
    buildTool gradle,
    clientFramework react,
    testFrameworks [protractor]
  }
  entities *
}

application {
  config {
    baseName invoice,
    applicationType microservice,
    packageName com.jhipster.demo.invoice,
    serviceDiscoveryType eureka,
    authenticationType jwt,
    prodDatabaseType mysql,
    buildTool gradle,
    serverPort 8081
  }
  entities Invoice, Shipment
}

application {
  config {
    baseName notification,
    applicationType microservice,
    packageName com.jhipster.demo.notification,
    serviceDiscoveryType eureka,
    authenticationType jwt,
    databaseType mongodb,
    cacheProvider no,
    enableHibernateCache false,
    buildTool gradle,
    serverPort 8082
  }
  entities Notification
}

/* Entities for Store Gateway */

/** Product sold by the Online store */
entity Product {
    name String required
    description String
    price BigDecimal required min(0)
    size Size required
    image ImageBlob
}

enum Size {
    S, M, L, XL, XXL
}

entity ProductCategory {
    name String required
    description String
}

entity Customer {
    firstName String required
    lastName String required
    gender Gender required
    email String required pattern(/^[^@\s]+@[^@\s]+\.[^@\s]+$/)
    phone String required
    addressLine1 String required
    addressLine2 String
    city String required
    country String required
}

enum Gender {
    MALE, FEMALE, OTHER
}

entity ProductOrder {
    placedDate Instant required
    status OrderStatus required
    code String required
    invoiceId Long
}

enum OrderStatus {
    COMPLETED, PENDING, CANCELLED
}

entity OrderItem {
    quantity Integer required min(0)
    totalPrice BigDecimal required min(0)
    status OrderItemStatus required
}

enum OrderItemStatus {
    AVAILABLE, OUT_OF_STOCK, BACK_ORDER
}

relationship OneToOne {
    Customer{user(login) required} to User
}

relationship ManyToOne {
 OrderItem{product(name) required} to Product
}

relationship OneToMany {
   Customer{order} to ProductOrder{customer(email) required},
   ProductOrder{orderItem} to OrderItem{order(code) required} ,
   ProductCategory{product} to Product{productCategory(name)}
}

service Product, ProductCategory, Customer, ProductOrder, OrderItem with serviceClass
paginate Product, Customer, ProductOrder, OrderItem with pagination

/* Entities for Invoice microservice */
entity Invoice {
    code String required
    date Instant required
    details String
    status InvoiceStatus required
    paymentMethod PaymentMethod required
    paymentDate Instant required
    paymentAmount BigDecimal required
}

enum InvoiceStatus {
    PAID, ISSUED, CANCELLED
}

entity Shipment {
    trackingCode String
    date Instant required
    details String
}

enum PaymentMethod {
    CREDIT_CARD, CASH_ON_DELIVERY, PAYPAL
}

relationship OneToMany {
    Invoice{shipment} to Shipment{invoice(code) required}
}

service Invoice, Shipment with serviceClass
paginate Invoice, Shipment with pagination
microservice Invoice, Shipment with invoice

/* Entities for notification microservice */

entity Notification {
    date Instant required
    details String
    sentDate Instant required
    format NotificationType required
    userId Long required
    productId Long required
}

enum NotificationType {
    EMAIL, SMS, PARCEL
}

microservice Notification with notification

Now let us invoke JHipster CLI to import this file

1
$ jhipster import-jdl app.jdl

This will create the store, invoice and notification folders and will do the below in each of the folders

  • Generate the appropriate application and entities configuration.

  • Generate the application and entities source code based on the configurations.

  • Install the NPM dependencies for the application.

Once the process is complete you should see the below on your console

1
2
3
4
5
6
7
8
9
Entity Product generated successfully.
Entity ProductCategory generated successfully.
Entity Customer generated successfully.
Entity ProductOrder generated successfully.
Entity OrderItem generated successfully.
Entity Invoice generated successfully.
Entity Shipment generated successfully.
Entity Notification generated successfully.
Congratulations, JHipster execution is complete!

Walk around the generated code to familiarize yourself.

Running the applications with Docker

Now that our applications are created its time to test them locally using Docker. To do this first let us generate some docker compose configurations using JHipster.

Create a new folder inside the ecommerce folder and run the JHipster docker-compose command

1
2
$ mkdir docker-compose && cd docker-compose
$ jhipster docker-compose

It will prompt you with a few questions, choose the answers as highlighted below

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
🐳  Welcome to the JHipster Docker Compose Sub-Generator 🐳
Files will be generated in folder: /home/deepu/workspace/temp/ecommerce/docker-compose
✔ Docker is installed

? Which *type* of application would you like to deploy? Microservice application

? Which *type* of gateway would you like to use? JHipster gateway based on Netflix Zuul

? Enter the root directory where your gateway(s) and microservices are located ../

3 applications found at /home/deepu/workspace/temp/ecommerce/
? Which applications do you want to include in your configuration? invoice, notification, store

? Which applications do you want to use with clustered databases (only available with MongoDB and Couchbase)? 

? Do you want to setup monitoring for your applications ? Yes, for logs and metrics with the JHipster Console (based on ELK and Zipkin)

? You have selected the JHipster Console which is based on the ELK stack and additional technologies, which one do you want to use ? Zipkin, for distributed tracing (only compatible with
 JHipster >= v4.2.0)

JHipster registry detected as the service discovery and configuration provider used by your apps
? Enter the admin password used to secure the JHipster Registry? admin

This will generate all the required docker-compose configurations for the stack and will also print out further instructions to build the docker images.

Note: In the latest JHipster versions we migrated to using Jib for creating Docker images. This is a huge improvement over the Docker Maven plugin that we were using, as a result the command to create an image has changed to ./gradlew -Pprod bootWar jibDockerBuild.

1
2
3
4
5
Docker Compose configuration generated with missing images!
To generate the missing Docker image(s), please run:
  ./gradlew -Pprod bootWar jibDockerBuild in /home/deepu/workspace/temp/ecommerce/invoice
  ./gradlew -Pprod bootWar jibDockerBuild in /home/deepu/workspace/temp/ecommerce/notification
  ./gradlew -Pprod bootWar jibDockerBuild in /home/deepu/workspace/temp/ecommerce/store

Follow the instructions and build the docker images. Once all 3 images are built run the below command from the docker-compose folder to fire everything up.

1
$ docker-compose up -d

Once the containers start you can stream the logs using below command

1
$ docker-compose logs -f

Now point your favorite browser to http://localhost:8080/ and see the E-Commerce microservice application in action.

Gateway application(Store)Gateway application(Store)

You can see the JHipster registry in action at http://localhost:8761/

JHipster RegistryJHipster Registry

And finally the JHipster console at http://localhost:5601/

JHipster Console- Kibana dashboardJHipster Console- Kibana dashboard

Once you are done playing around, you can shut everything down by running the below command on the docker-compose folder

1
docker-compose down

Hope you had fun creating microservices using JHipster. To learn how to convert a JHipster monolith to microservices check out my book “Full Stack Development with JHipster” on Amazon and Packt.

In the coming weeks, I’ll write some posts about deploying this microservice stack to various cloud providers like GCP, Azure, AWS, Heroku and so on.

If you like JHipster don’t forget to give it a star on Github.

If you like this article, please leave a like or a comment.

You can follow me on Twitter and LinkedIn.

My other related posts:

  1. Deploying JHipster Microservices on Azure Kubernetes Service (AKS)

  2. JHipster microservices with Istio service mesh on Kubernetes

Originally published in Medium on Sep 22, 2018

comments powered by Disqus