/
README.md
1118 lines (824 loc) · 35 KB
/
README.md
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Cloud-native Java EE Microservices with KumuluzEE: REST service using config, discovery, security, metrics, logging and fault tolerance
A goal of this tutorial is to develop a cloud-native Java EE microservice application, using KumuluzEE microservice framework and KumuluzEE extensions.
We will develop a sample application for managing customers and their orders. The application consists of two microservices; one for managing customer entities and one for managing order entities. We will demonstrate important cloud-native concepts and functionalities that are essential in microservice architecture, such as dynamic configuration (with config server), service discovery, fault tolerance, centralized logging, performance metrics collection, and security mechanisms.
We will use the following KumuluzEE extensions:
- KumuluzEE REST for implementation of filtering, sorting and pagination on REST resources,
- KumuluzEE Config for dynamic reconfiguration of microservices with the use of configuration servers,
- KumuluzEE Discovery for service registration and service discovery,
- KumuluzEE Fault Tolerance for improving the resilience of microservices,
- KumuluzEE Logs for advanced centralized logging,
- KumuluzEE Metrics for collection of performance metrics,
- KumuluzEE Security for securing developed REST endpoints.
First, we will create a Maven project that will contain both our microservices. We will then implement both microservices and use the KumuluzEE extensions to implement configuration, service discovery, fault tolerance, logging, metrics and security mechanisms.
Complete source code can be found on the GitHub repository.
## Create Maven project
The root Maven project will hold both developed microservices. Each microservice will be structured into three modules;
persistence, with JPA Entities and database access logic, business-logic, with CDI beans holding implementation of
business logic, and api module, exposing business logic in form of RESTful services. The full structure should be as
follows:
- kumuluzee-tutorial
- customers
- customers-api
- customers-business-logic
- customers-persistence
- orders
- orders-api
- orders-business-logic
- orders-persistence
We will use the `pom.xml` file of the root project (kumuluzee-tutorial) to define all properties and dependencies which
will be used in other modules. It should look like this:
```xml
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- other properties -->
<kumuluzee.version>2.4.1</kumuluzee.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.kumuluz.ee</groupId>
<artifactId>kumuluzee-bom</artifactId>
<version>${kumuluzee.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- other dependencies -->
</dependencies>
</dependencyManagement>
```
## Customer microservice
First, we will implement the customer microservice that will provide CRUD functionalities for the custumer objects.
### Maven dependencies
Before we start writing code, we have to add all the Maven dependencies that we will need in this microservice.
#### Persistence module
In the persistence module we will need the JPA dependency for accessing the database. We will use Postgresql database,
hence we also need the Postgresql JDBC driver.
```xml
<dependencies>
<dependency>
<groupId>com.kumuluz.ee</groupId>
<artifactId>kumuluzee-jpa-eclipselink</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
</dependencies>
```
#### Business-logic module
Business logic module will implement the business logic in CDI beans. We need to add a CDI implementation, which is
available in the `kumuluzee-cdi-weld` module. We will also need JPA entities and DB access logic, defined in our
`persistence` module.
```xml
<dependencies>
<dependency>
<groupId>com.kumuluz.ee</groupId>
<artifactId>kumuluzee-cdi-weld</artifactId>
</dependency>
<dependency>
<groupId>com.kumuluz.ee.samples.tutorial</groupId>
<artifactId>persistence</artifactId>
</dependency>
</dependencies>
```
#### API module
API module will be the core module of our microservice that will be executed. It needs `kumuluzee-core`
and `kumuluzee-servlet-jetty` dependecies. It will expose business logic as a RESTful services, so it requires the `kumuluzee-jax-rs-jersey` dependency and the `business-logic` module. We will also add the `kumuluzee-maven-plugin` to package our microservice in a
Uber JAR.
```xml
<dependencies>
<dependency>
<groupId>com.kumuluz.ee</groupId>
<artifactId>kumuluzee-core</artifactId>
</dependency>
<dependency>
<groupId>com.kumuluz.ee</groupId>
<artifactId>kumuluzee-servlet-jetty</artifactId>
</dependency>
<dependency>
<groupId>com.kumuluz.ee</groupId>
<artifactId>kumuluzee-jax-rs-jersey</artifactId>
</dependency>
<dependency>
<groupId>com.kumuluz.ee.samples.tutorial</groupId>
<artifactId>business-logic</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.kumuluz.ee</groupId>
<artifactId>kumuluzee-maven-plugin</artifactId>
<version>${kumuluzee.version}</version>
<executions>
<execution>
<id>package</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
```
### Develop microservice
Now it is time to implement our microservice.
#### Add config
First, we will add the basic KumuluzEE configuration in a `config.yml` configuration file. You can read more about
KumuluzEE configuration framework [here](https://github.com/kumuluz/kumuluzee/wiki/Configuration). In our case, we
will specify service name, version, environment in which the microservice is deployed, and set the server http port and
the base-url.
```bash
kumuluzee:
name: customer-service
env:
name: dev
version: 1.0.0
server:
base-url: http://localhost:8080
http:
port: 8080
```
#### Develop persistence module
Before we implement the persistance module, we have to run the database instance. We will use Docker to achieve that. To
run a new Postgresql instance in Docker, use the following command:
```bash
docker run -d --name postgres-customers -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=customer -p 5432:5432 postgres:latest
```
##### Create entity
In this step, we will define a JPA entity that will be used to represent the customers. We will create the following class:
```bash
@Entity(name = "customer")
@NamedQueries(value =
{
@NamedQuery(name = "Customer.getAll", query = "SELECT c FROM customer c")
})
@UuidGenerator(name = "idGenerator")
public class Customer {
@Id
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
private String address;
@Column(name = "date_of_birth")
private Date dateOfBirth;
// getter and setter methods
}
```
##### Define JDBC datasource in config
A JDBC datasource has to be defined with KumuluzeEE configuration framework. We have to specify the datasource name and the database
connection properties with the following configuration keys:
```bash
kumuluzee:
datasources:
- jndi-name: jdbc/CustomersDS
connection-url: jdbc:postgresql://localhost:5432/customer
username: postgres
password: postgres
pool:
max-size: 20
```
##### Define persistance.xml
In order for our application to connect to the defined datasource, we have to specify a persistence unit in
the `persistence.xml`. The following configuration will automatically execute a SQL script on microservice startup to
populate the database with the development data. Such configuration is useful for development purposes, but not for
production environments.
```xml
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1">
<persistence-unit name="customers-jpa" transaction-type="RESOURCE_LOCAL">
<non-jta-data-source>jdbc/CustomersDS</non-jta-data-source>
<class>com.kumuluz.ee.samples.tutorial.customers.Customer</class>
<properties>
<property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
<property name="javax.persistence.schema-generation.create-source" value="metadata"/>
<property name="javax.persistence.sql-load-script-source"
value="sql-scripts/init-customers.sql" />
<property name="javax.persistence.schema-generation.drop-source" value="metadata"/>
</properties>
</persistence-unit>
</persistence>
```
#### Develop business logic module
Business logic module will implement CRUD operations for managing customer entities. It will be implemented as an
application scoped CDI bean. A `beans.xml` file has to be placed in `resources/META-INF` folder in order to enable CDI.
A CDI bean with business logic should look like this:
```java
@RequestScoped
public class CustomersBean {
@PersistenceContext(unitName = "customers-jpa")
private EntityManager em;
public List<Customer> getCustomers(){
Query query = em.createNamedQuery("Customer.getAll", Customer.class);
return query.getResultList();
}
public Customer getCustomer(String customerId) {
Customer customer = em.find(Customer.class, customerId);
if (customer == null) {
throw new NotFoundException();
}
return customer;
}
public Customer createCustomer(Customer customer) {
try {
beginTx();
em.persist(customer);
commitTx();
} catch (Exception e) {
rollbackTx();
}
return customer;
}
public Customer putCustomer(String customerId, Customer customer) {
Customer c = em.find(Customer.class, customerId);
if (c == null) {
return null;
}
try {
beginTx();
customer.setId(c.getId());
customer = em.merge(customer);
commitTx();
} catch (Exception e) {
rollbackTx();
}
return customer;
}
public boolean deleteCustomer(String customerId) {
Customer customer = em.find(Customer.class, customerId);
if (customer != null) {
try {
beginTx();
em.remove(customer);
commitTx();
} catch (Exception e) {
rollbackTx();
}
} else
return false;
return true;
}
}
```
#### Develop API module
The API module will expose the business logic as a set of RESTful services. First, add `beans.xml` file to
`resources/META-INF` in order to enable CDI.
##### Application class
To enable JAX-RS, we first add a class that extends `javax.ws.rs.core.Application` and annotate it with
`@ApplicationPath`.
```java
@ApplicationPath("/v1")
public class CustomerApplication extends Application {
}
```
##### JAX-RS resource
In the next step, we add a resource class that will expose the business logic. It should look like this:
```java
@RequestScoped
@Path("/customers")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class CustomersResource {
@Inject
private CustomersBean customersBean;
@GET
public Response getCustomers() {
List<Customer> customers = customersBean.getCustomers();
return Response.ok(customers).build();
}
@GET
@Path("/{customerId}")
public Response getCustomer(@PathParam("customerId") String customerId) {
Customer customer = customersBean.getCustomer(customerId);
if (customer == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return Response.status(Response.Status.OK).entity(customer).build();
}
@POST
public Response createCustomer(Customer customer) {
if ((customer.getFirstName() == null || customer.getFirstName().isEmpty()) || (customer.getLastName() == null
|| customer.getLastName().isEmpty())) {
return Response.status(Response.Status.BAD_REQUEST).build();
} else {
customer = customersBean.createCustomer(customer);
}
if (customer.getId() != null) {
return Response.status(Response.Status.CREATED).entity(customer).build();
} else {
return Response.status(Response.Status.CONFLICT).entity(customer).build();
}
}
@PUT
@Path("{customerId}")
public Response putZavarovanec(@PathParam("customerId") String customerId, Customer customer) {
customer = customersBean.putCustomer(customerId, customer);
if (customer == null) {
return Response.status(Response.Status.NOT_FOUND).build();
} else {
if (customer.getId() != null)
return Response.status(Response.Status.OK).entity(customer).build();
else
return Response.status(Response.Status.NOT_MODIFIED).build();
}
}
@DELETE
@Path("{customerId}")
public Response deleteCustomer(@PathParam("customerId") String customerId) {
boolean deleted = customersBean.deleteCustomer(customerId);
if (deleted) {
return Response.status(Response.Status.GONE).build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
}
}
}
```
##### KumuluzEE Rest
Now it is time to add our first KumuluzEE extension. We will use KumuluzEE Rest to add best practices for developing
RESTful services, such as sorting, filtering and pagination.
First, we add a maven dependency:
```xml
<dependency>
<groupId>com.kumuluz.ee.rest</groupId>
<artifactId>kumuluzee-rest-core</artifactId>
<version>1.1.0</version>
</dependency>
```
Then, we inject `UriInfo` object into the REST resource. `UriInfo` holds the data about the request's URL and will be used as
an input to the KumuluzEE REST extension. We inject it as:
```java
@Context
protected UriInfo uriInfo;
```
We add a new REST endpoint, which will be used to get filtered, sorted or paginated requests:
```java
@GET
@Path("/filtered")
public Response getCustomersFiltered() {
List<Customer> customers;
customers = customersBean.getCustomersFilter(uriInfo);
return Response.status(Response.Status.OK).entity(customers).build();
}
```
We also add a method to the CDI bean. It uses the `JPAUtils` object to query filtered entities:
```java
public List<Customer> getCustomersFilter(UriInfo uriInfo) {
QueryParameters queryParameters = QueryParameters.query(uriInfo.getRequestUri().getQuery()).defaultOffset(0)
.build();
List<Customer> customers = JPAUtils.queryEntities(em, Customer.class, queryParameters);
return customers;
}
```
###### Test
We can test the new endpoint with the following URLs.
Pagination:
- localhost:8080/v1/customers/filtered?offset=1&limit=1
Sorting:
- localhost:8080/v1/customers/filtered?order=dateOfBirth DESC
Filtering:
- localhost:8080/v1/customers/filtered?filter=firstName:EQ:James
- localhost:8080/v1/customers/filtered?filter=firstName:NEQ:James
- localhost:8080/v1/customers/filtered?filter=lastName:LIKE:S%
- localhost:8080/v1/customers/filtered?where=dateOfBirth:GT:'2010-01-01T00:00:00%2B00:00'
##### Additional JAX-RS features
In this section we will add some additional JAX-RS features to our microservice.
###### Configure Jackson serializer
A Jackson serializer will be used to correctly display dates in our microservice. We implement it as a provider class
that extends `javax.ws.rs.ext.ContextResolver`:
```java
@Provider
public class JacksonProducer implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public JacksonProducer() {
mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
mapper.setDateFormat(dateFormat);
}
@Override
public ObjectMapper getContext(Class<?> aClass) {
return mapper;
}
}
```
###### Exception mapper
An exception mapper will be used to map errors into readable error messages. First, we define a DTO that will hold
the error message:
```java
public class Error {
private Integer status;
private String code;
private String message;
// getter and setter methods
}
```
Then we add a mapper that will wrap the `NotFoundExceptions` into newly defined Error objects:
```java
@Provider
@ApplicationScoped
public class NotFoundExceptionMapper implements ExceptionMapper<NotFoundException> {
@Override
public Response toResponse(NotFoundException e) {
Error error = new Error();
error.setStatus(404);
error.setCode("resource.not.found");
error.setMessage(e.getMessage());
return Response
.status(Response.Status.NOT_FOUND)
.entity(error)
.build();
}
}
```
### Run and test the microservice
We have two options for running our microservice:
1. We can use an IDE of our choice to run our microservice. We simply run it as Java application, with the main class set
to `com.kumuluz.ee.EeApplication`.
2. We can use Maven to build it and `java` to run the Uber JAR.
```bash
mvn clean package
```
```bash
java -jar target/customers-api-1.0.0-SNAPSHOT.jar
```
We can test our microservice by accessing the following URL: http://localhost:8080/v1/customers
### Package microservice as Docker image and run it
Now, it is time to package our microservice as a Docker image and run it as a Docker container. First, we will
specify a dockerfile with the information on image-building process:
```yaml
FROM openjdk:8-jre-alpine
RUN mkdir /app
WORKDIR /app
ADD ./customers-api/target/customers-api-1.0.0-SNAPSHOT.jar /app
EXPOSE 8080
CMD ["java", "-jar", "customers-api-1.0.0-SNAPSHOT.jar"]
```
To create the Docker image, perform the folowig steps:
- build the microservice with `mvn clean package`.
- build the Docker image with `docker build -t customers-api:1.00 .`.
To run the Docker container from the built image run: `docker run -e
KUMULUZEE_DATASOURCES0_CONNECTIONURL=jdbc:postgresql://databaseUrl:5432/customer -p 8080:8080 customers-api:1.00`
#### Docker compose
Instead of running Postgresql and microservice seperately, we could package them as a Docker compose application with
the following configuration:
```yaml
version: "3"
services:
postgres:
image: postgres:latest
environment:
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=customer
ports:
- "5432:5432"
customer-service:
image: customers-api:1.00
environment:
- KUMULUZEE_DATASOURCES0_CONNECTIONURL=jdbc:postgresql://postgres:5432/customer
ports:
- "8080:8080"
depends_on:
- postgres
```
## Order microservice
Now we will develop the second microservice that will be used for managing the data about orders. Each order will be
related to one customer.
### Develop microservice
To develop the order microservice, we have to repeat similar steps as with the customer microservice.
First, we run another Postgresql database instance for storing the order data:
```bash
docker run -d --name postgres-orders -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=order -p 5433:5432 postgres:latest
```
Then we define the `Order` JPA entity. We extend the `Customer` entity with the data about its orders:
```java
@Transient
private List<Order> orders;
```
Since the orders field is annotated with `@Transient`, the orders will not get fetched and stored into the database by JPA.
Instead, we will retrieve them from the order microservice.
All the other steps of developing the order microservice are very similar to the customer microservice and will not be
repeated here.
## Connect the two microservices
We will now extend the customer microservice so that it will return the orders of each queried customer. We will extend
the business logic CDI bean with the remote http call to the order service. We have to add the following fields and methods:
```java
private ObjectMapper objectMapper;
private HttpClient httpClient;
private String basePath;
@Inject
private CustomersBean customersBean;
@PostConstruct
private void init() {
httpClient = HttpClientBuilder.create().build();
objectMapper = new ObjectMapper();
basePath = "http://localhost:8081/v1/";
}
public Customer getCustomer(String customerId) {
Customer customer = em.find(Customer.class, customerId);
if (customer == null) {
throw new NotFoundException();
}
List<Order> orders = customersBean.getOrders(customerId);
customer.setOrders(orders);
return customer;
}
public List<Order> getOrders(String customerId) {
try {
HttpGet request = new HttpGet(basePath + "/v1/orders?where=customerId:EQ:" + customerId);
HttpResponse response = httpClient.execute(request);
int status = response.getStatusLine().getStatusCode();
if (status >= 200 && status < 300) {
HttpEntity entity = response.getEntity();
if (entity != null)
return getObjects(EntityUtils.toString(entity));
} else {
String msg = "Remote server '" + basePath + "' is responded with status " + status + ".";
log.error(msg);
throw new InternalServerErrorException(msg);
}
} catch (IOException e) {
String msg = e.getClass().getName() + " occured: " + e.getMessage();
log.error(msg);
throw new InternalServerErrorException(msg);
}
return new ArrayList<>();
}
```
We can test the newly developed feature by accessing the following URL: localhost:8080/v1/customers/1 It should
see the customer object with two orders.
## KumuluzEE Config
We will now add the option to disable the remote calls to the order service using KumuluzEE configuration framework. We will also integrate a configuration server (etcd and Consul are supported). First, we add a configuration key into the `config.yml` file:
```yalm
rest-properties:
external-dependencies:
order-service:
enabled: true
```
In the next step we add a properties bean that will load, hold and update the configuration properties at runtime.
```java
@ApplicationScoped
@ConfigBundle("rest-properties")
public class RestProperties {
@ConfigValue(value = "external-dependencies.order-service.enabled", watch = true)
private boolean orderServiceEnabled;
// getter and setter methods
}
```
We add an if statement to the `CustomerBean` class:
```java
if (restProperties.isOrderServiceEnabled()) {
List<Order> orders = customersBean.getOrders(customerId);
customer.setOrders(orders);
}
```
### Configuration server
Now it is time to add the configuration server, which will store the configuration remotely in the server instead of the file (or environemnt settings or properties). First, we add the KumuluzEE Config extension that will add the configuration
server as one of the available configuration sources. We use etcd in this example, although Conusl is also supported:
```bash
<dependency>
<groupId>com.kumuluz.ee.config</groupId>
<artifactId>kumuluzee-config-etcd</artifactId>
<version>${kumuluzee-config.version}</version>
</dependency>
```
We will use etcd as configuration server. We could replace it with Consul by just replacing the Maven dependency to
`kumuluzee-config-consul`.
We can now run etcd server instance with the following Docker command:
```bash
$ docker run -d -p 2379:2379 \
--name etcd \
--volume=/tmp/etcd-data:/etcd-data \
quay.io/coreos/etcd:latest \
/usr/local/bin/etcd \
--name my-etcd-1 \
--data-dir /etcd-data \
--listen-client-urls http://0.0.0.0:2379 \
--advertise-client-urls http://0.0.0.0:2379 \
--listen-peer-urls http://0.0.0.0:2380 \
--initial-advertise-peer-urls http://0.0.0.0:2380 \
--initial-cluster my-etcd-1=http://0.0.0.0:2380 \
--initial-cluster-token my-etcd-token \
--initial-cluster-state new \
--auto-compaction-retention 1 \
-cors="*"
```
We can edit the values inside etcd with the following [editor](henszey.github.io/etcd-browser/).
Before we can access the configuration server, we have to provide access configuration in the `config.yml` file:
```yaml
config:
etcd:
hosts: http://192.168.99.100:2379
```
We can now override the configuration from the configuration file and disable the external dependency calls by setting the
following etcd key to `false`:
- /environments/dev/services/customer-service/1.0.0/config/rest-properties/external-dependencies/order-service/enabled
## KumuluzEE Discovery
In this step, we will add the KumuluzEE Discovery extension to enable service registration and dynamic discovery instead of
manually wiring the microservice URL. This is particularly useful in Kubernetes and other container orchestration environments. We will register the order microservice and use service discovery in the customer microservice.
In this example, we will use etcd for service discovery. Consul is supported as well.
Add the following dependency:
```xml
<dependency>
<groupId>com.kumuluz.ee.discovery</groupId>
<artifactId>kumuluzee-discovery-etcd</artifactId>
<version>${kumuluzee-discovery.version}</version>
</dependency>
```
We have to provide the configuration to access the etcd server:
```yaml
discovery:
etcd:
hosts: http://192.168.99.100:2379
```
### Register service
To register the order service, add the `@RegisterService` to the Application class:
```java
@ApplicationPath("/v1")
@RegisterService
public class OrdersApplication extends Application {
}
```
### Discover service
We will use service discovery in `CustomerBean` to get the URL of the registered order service. We retrieve service
URL with simple injection:
```java
@Inject
@DiscoverService(value = "order-service", environment = "dev", version = "*")
private Optional<String> basePath;
```
We can now remove manual wiring from the `init()` method.
We could also use Consul instead of etcd by simply changing Maven dependency to `kumuluzee-discovery-consul`.
## Fault tolerance
To achieve high resilience of our microservice application, we have to provide adequate fault tolerance mechanisms.
We will use KumuluzEE Fault Tolerance extension. First, add the following Maven dependency:
```xml
<dependency>
<groupId>com.kumuluz.ee.fault.tolerance</groupId>
<artifactId>kumuluzee-fault-tolerance-hystrix</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
```
### Adding fallback mechanisms
The most critical point of failure in our application is the communication between the two microservices. We do not want the
customer microservice to be unavailable if the order microservice fails. To achieve this, we will add the fault tolerance
fallback mechanisms to the `getOrders` method, We will enable cirrcuit breaker, fallback and timeout:
```java
@RequestScoped
@GroupKey("orders")
public class CustomersBean {
...
@CircuitBreaker(failureRatio = 0.3)
@Fallback(fallbackMethod = "getOrdersFallback")
@CommandKey("http-get-order")
@Timeout(value = 500)
public List<Order> getOrders(String customerId) {
}
public List<Order> getOrdersFallback(String customerId) {
return new ArrayList<>();
}
}
```
We enabled fault tolerance with the annotation `@GroupKey` on the class. We added annotations on the `getOrders` method.
Annotation `@CircuitBreaker` opens circuit breaker if the request rate is higher than 30%. `@Timeout` prevents the
method to wait for the response longer than 500 ms. With `@Fallback` we defined a method that will be called in case
errors occcur.
## Logging
In the microservice architecture, logs should be collected in the central log management system. We will use the KumuluzEE Logs to
enable advanced logging mechanisms and send logs to elastic Stack using Logstash. First, add the following Maven dependency:
```xml
<dependency>
<groupId>com.kumuluz.ee.logs</groupId>
<artifactId>kumuluzee-logs-log4j2</artifactId>
<version>${kumuluzee-logs.version}</version>
</dependency>
```
### Configure Log4j2
Add the `log4j2` configuration in the configuration file:
```yaml
kumuluzee:
logs:
config-file:
'<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="tutorial-logging">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="%d %p %marker %m %X %ex %n"/>
</Console>
<!-- A socket definition for sending logs into Logstash using TCP and JSON format.-->
<!--<Socket name="logstash" host="192.168.99.100" port="5043" protocol="tcp">
<JSONLayout complete="false" compact="true" eventEol="true" charset="UTF-8" properties="true"/>
</Socket>-->
</Appenders>
<Loggers>
<!-- Default logger -->
<Root level="trace">
<AppenderRef ref="console"/>
<AppenderRef ref="logstash"/>
</Root>
</Loggers>
</Configuration>'
```
This configuration outputs logs to the console and to the Logstash instance on the specified address.
### Log endpoint calls
To enable automatic logging of all REST endpoint calls, add the `@Log` annotation to the `CustomerResource` class.
To log additional context parameters, such as microservice name, version and environment, you can implement the
interceptor that will inject the data to the logging system:
```java
@Log
@Interceptor
@Priority(Interceptor.Priority.PLATFORM_BEFORE)
public class LogContextInterceptor {