Microservice project with reactive approach for the bank.
Below you will find some information on how to perform common tasks.
- Updating to New Releases
- Sending Feedback
- Briefing
- Project Structure
- Supported Language Features and Tools
- Integrating with an API Backend
- Sever side
- Build
- Papers
When you run application
, it always creates the project with the latest version of scripts
so you’ll get all the new features and improvements in newly created apps automatically.
To update an existing project to a new version, open the changelog, find the version you’re currently on, and apply the migration for the newer versions.
We commit to keeping the breaking changes minimal, so you can upgrade painlessly.
We are always open to your feedback.
- Provide responsiveness under load
- The system remains functional even if one of the components fails.
- The system should take up the optimal amount of resources in each time period.
- Communication between services must occur via asynchronous messages. Each element of the system requests information from another element, but does not expect to get the result immediately. Instead, it continues to perform its tasks.
- Configure Eureka Server, Eureka Client (User-Service, Stonks-Service).
- Use the Postgres, Mongo database, and write reactive services on Spring 5.
- Make friends with the services and make them communicate via RestTemplate, Feign Client, WebClient for the statistic.
- Add the Zuul API Gateway, a Ribbon load balancer.
- Have a Space-service with a spare replica.
- Configure Load Balancer, Configure Config Server And Config Client.
- Add Token Security.
- Configure Docker Swarm
- Make several replicas for Eureka Server and other services and make them work together.
- Add a ELK (Elastic Search Kibana) to view logs.
- Add Spring Cloud sleutch and Spring Cloud Stream together with Kafka and RabbitMQ.
- Get acquainted with the websocket and Rsocket protocols and build a connection between services.
- Use Saga Pattern
- Explore Axon
After creation, your project should look like this:
Bank-Services/
README.md
CHANGELOG.md
eureka-server/
...
zuul-serice/
...
security-service/
...
stonks-service/
...
user-service/
...
fine-card-service/
...
benchmark/
...
bucket-service/
...
config-server/
...
ms-config-service/
...
order-service/
...
tools/
...
.gitignore
You can delete or rename files.
Eureka Server (the service registry) - application, that contain meta about all client service applications.
Each service register
on Eureka and Eureka knows all services, running on each port and IP address.
Service interacts with Server in this way:
- Client register information about running instance on Eureka.
- Every 30 seconds Client send a request to the server to inform that it still alive, if server doesn't see update within 90 seconds, it just removes instance.
- Eureka Client get information from the server and store in itself as cache and update every 30 seconds. This meta automatically update.
- Getting the updates, Client verify information with the server, comparing count of instances and if it has error, the server will send again information.
- At the end of the running Client send cancellation request to the server. In this way instance will be deleted from registry.
Client that was used 3 unsuccessful attempts with interval in 30 seconds will be deleted.
Interaction of servers with each other is the same as with Client and Server. If has problem it try to check all peer nodes or protect already available.
Gateway, an intermediate level between users and services.
Filters:
Pre-filters
: Performed before routing and can be used for things like authentication, routing and request processing, speed limits, DDoS protection.Endpoint-filters
: filters-are responsible for processing the request based on performing pre-filters (health check responses, static error responses, 404).Post-filters
: They are executed after receiving a response from the source and can be used for metrics, to generate a response for the user, or to add any custom headers.
Represent of a simple crud application with Mongo - DB, WebFlux - reactive, Lombok - convenience.
Will communicate with stonks-service to get data from BD, i.e. not directly.
In case failure of BD or service, it will use Hystrix, that instead of error will return the default value or use replica.
The user won't notice anything.
Also, user service use Ribbon - load balancer between replicas of other services.
Contains links to the storage with General settings. To avoid writing the same code in different services, use a common settings store.
When you start your service, it will register with Eureka and access the config-server,
which will direct it to the remote storage with the settings.
Then you get the settings among all of them as follows
:
It looks at {application} - the name of the service, and then at {profile} - default profile is default.
This project supports a superset of the latest Java standard.
In addition to Java SE 11 syntax features, it also includes:
- Spring Boot
- Spring Security
- Spring WebFlux
- Spring Cloud Netflix
- Spring Data
- Spring AOP
- Project Lombok
- Docker
- Bash
Learn more about: Microservices, RxJava, Message Sevice.
Note that the project only includes a few Java SE 11:
- URL: /create
- Method:
POST
- Required:
title=[String, NotNull, Length<10]
- Optional:
id=[String]
- Data Params:
{
"title": "Spring",
"description": "Cassandra",
"personalNumber": 1973,
"imageLink": "https://..."
}
- Success Response:
- Code: 200
Content:
- Code: 200
{
id : 12,
title : "Java",
description : "OOP",
personalNumber : 200,
imageLink : "https://...."
}
- Error Response:
-
Code: 400 BAD REQUEST
Content:{ title : "Max length is 10" }
-
Code: 401 UNAUTHORIZED
Content:{ error : "Log in" }
-
- Sample Call:
$.ajax({
url: "/create",
contentType: "application/json",
type : "POST",
data : JSON.stringify({ "id": 123, "title": "Spring", "description": "Cassandra", "personalNumber": 1973, "imageLink": "https://..."}),
success : function() {
console.log("GJ");
}
});
POST http://localhost:8081/create
Content-Type: application/json
{
"id": 123,
"title": "Spring Data",
"description": "Cassandra",
"personalNumber": 1973,
"imageLink": "https://..."
}
- Notes: hasRole ("USER","ADMIN")
Meta Information
- URL: /
- Method:
GET
,POST
,PUT
,DELETE
,PATCH
- Required: None
- Success Response:
- Code: 200
Content:Stonks-Service running at port: 8081
- Code: 200
- Sample Call:
$.ajax({ url: "/", dataType: "String", type : "GET", success : function(r) { console.log(r); } });
GET http://localhost:8081/ Accept: application/json
- Notes: hasRole ("GUEST","USER","ADMIN")
Read All Data
- URL:
/getAll
,/show
- Feign Client (on User-Service side) ,/data
- Rest Template (on User-Service side) - Method:
GET
- Required: None
- Success Response:
- Code: 200
Content:
- Code: 200
[
{
id : 12,
title : "Java",
description : "OOP",
personalNumber : 200,
imageLink : "https://...."
},
{
id : 2,
title : "Java",
description : "Stream API",
personalNumber : 437,
imageLink : "https://...."
}
]
- Sample Call:
$.ajax({
url: "/getAll",
dataType: "json",
type : "GET",
success : function(r) {
console.log(r);
}
});
GET http://localhost:8081/getAll
Accept: application/json
- Notes: hasRole ("GUEST","USER","ADMIN")
Read By ID
-
URL:
read/{id}
-
Method:
GET
-
Required: None
-
Success Response:
- Code: 200
Content:
- Code: 200
{
id : 12,
title : "Java",
description : "OOP",
personalNumber : 200,
imageLink : "https://...."
}
-
Error Response:
- Code: 404 NOT FOUND
- Code: 404 NOT FOUND
-
Sample Call:
$.ajax({ url: "/get/{id}", dataType: "json", type : "GET", success : function(r) { console.log(r); } });
GET http://localhost:8081/get/{id} Accept: application/json
-
Notes: hasRole ("GUEST","USER","ADMIN")
Read All Data, SSE
- URL:
/stream/stonks
) - Method:
GET
- Required: None
- Success Response:
- Code: 200
Content:
- Code: 200
[
{
id : 12,
title : "Java",
description : "OOP",
personalNumber : 200,
imageLink : "https://...."
},
{
id : 2,
title : "Java",
description : "Stream API",
personalNumber : 437,
imageLink : "https://...."
}
]
- Notes: hasRole ("GUEST","USER","ADMIN")
Read Default Data Every 1 Second, SSE
-
URL:
/stream/stonks/default
) -
Method:
GET
-
Required: None
-
Success Response:
- Code: 200
Content:
- Code: 200
{
id : "val",
title : "Python",
description : "default theme",
personalNumber : 0,
imageLink : "https://...."
}
- Notes: hasRole ("GUEST","USER","ADMIN")
Read All Data Every N Second, SSE
-
URL:
/stream/stonks/delay
) -
Method:
GET
-
Required: None
-
Success Response:
- Code: 200
Content:
- Code: 200
[
{
id : "15",
title : "PHP",
description : "MVP",
personalNumber : 789456,
imageLink : "https://...."
},
{
}
]
- Notes: hasRole ("GUEST","USER","ADMIN")
Update By ID
- Method:
UPDATE
- Required:
title=[String, NotNull, Length<10]
- Optional:
id=[String]
- Data Params:
{
"title": "Spring",
"description": "Cassandra",
"personalNumber": 1973,
"imageLink": "https://..."
}
- Success Response:
- Code: 200
Content:
- Code: 200
{
id : 12,
title : "Java",
description : "OOP",
personalNumber : 200,
imageLink : "https://...."
}
- Error Response:
-
Code: 404 NOT FOUND
-
Code: 400 BAD REQUEST
Content:{ title : "Max length is 10" }
-
Code: 401 UNAUTHORIZED
Content:{ error : "Log in" }
-
- Sample Call:
$.ajax({
url: "/update",
contentType: "application/json",
type : "UPDATE",
data : JSON.stringify({ "id": 123, "title": "Spring", "description": "Cassandra", "personalNumber": 1973, "imageLink": "https://..."}),
success : function() {
console.log("GJ");
}
});
POST http://localhost:8081/update
Content-Type: application/json
{
"id": 123,
"title": "Spring Data",
"description": "Cassandra",
"personalNumber": 1973,
"imageLink": "https://..."
}
- Notes: hasRole ("USER","ADMIN")
Delete By ID
-
URL:
delete/{id}
-
Method:
DELETE
-
Required: None
-
Success Response:
- Code: 200
Content:
{ id : 12, title : "Java", description : "OOP", personalNumber : 200, imageLink : "https://...." }
-
Error Response:
- Code: 404 NOT FOUND
- Code: 404 NOT FOUND
- Code: 200
-
Sample Call:
$.ajax({ url: "/delete/{id}", dataType: "json", type : "GET", success : function(r) { console.log(r); } });
GET http://localhost:8081/delete/{id} Accept: application/json
-
Notes: hasRole ("USER","ADMIN")
Delete All Data
-
URL:
/deleteAllStonks
) -
Method:
DELETE
-
Required: None
-
Success Response:
- Code: 200
Content: NONE
- Code: 200
-
Sample Call:
$.ajax({
url: "/deleteAllStonks",
dataType: "json",
type : "DELETE",
success : function(r) {
console.log(r);
}
});
DELETE http://localhost:8081/deleteAllStonks
Accept: application/json
- Notes: hasRole ("USER","ADMIN")
- Add custom TCP port in the security group.
- Connect via ssh
ssh "<key>" ec2-user@user2-**-**-**-**-**.compute-1.amazonaws.com
. - Add new user "developer"
sudo useradd -m -d /home/developer developer
. - Setting password
passwd developer
. - Add user in group
usermod -a -G wheel developer
. - Open ssh config file
sudo nano /etc/ssh/sshd_config
. - Change lines.
Port 22 -> Port 12
PaermitRootLogin yes -> PermitRootLogin no
+ AllowUsers developer
- Open new user's bashrc file.
cd /home/developer
nano .bashrc
- Add line
force_color_promt=yes
. - Reload.
(sudo systemctl enable sshd.service)
(sudo systemctl start sshd.service)
sudo systemctl stop sshd.service
sudo systemctl start sshd.service
- Connect via ssh
ssh [email protected]
. - Install sudo
apt-get install sudo
. - Add new user "developer"
useradd -m -d /home/developer developer
. - Setting password
passwd developer
. - Add user in group
usermod -a -G sudo developer
. - Open ssh config file
nano /etc/ssh/sshd_config
. - Change lines.
Port 22 -> Port 12
PaermitRootLogin yes -> PermitRootLogin no
+ AllowUsers developer
- Open new user's bashrc file.
cd /home/developer
nano .bashrc
- Add line
force_color_promt=yes
. - Reload.
service ssh reload
docker pull mongo
- merge last version with docker-hub.docker images
- check image.docker run mongo
- launch mongo. default port is 27017 or specifydocker run mongo --port 27017
.mongo
- get into mongo shell.- In case if you start stonks service
use stonksdb
- where stonksdb is the name of a database (see the application.yml) andshow collection
to see stonks object (see the Stonks class).
Ubuntu 18
sudo apt-get update
- update repositorysudo apt-get install openjdk-8-jre
- install java
- Build:
./mvnw clean install
- Run:
./mvnw spring-boot:run
Service
mvn install
- create jar filejava -jar -Dserver.port=8086 stonks-service-0.0.1-SNAPSHOT.jar
- start the application on different port
Server
/etc/hosts
add records
127.0.0.1 my-eureka-server.com
127.0.0.1 my-eureka-server-ru.com
127.0.0.1 my-eureka-server-us.com
127.0.0.1 my-eureka-server-fr.com
- Start
java -jar -Dspring.profiles.active=france eureka-server-0.0.1-SNAPSHOT.jar
java -jar -Dspring.profiles.active=united-states eureka-server-0.0.1-SNAPSHOT.jar
- Browser
http://my-eureka-server-us.com:9001
http://my-eureka-server-fr.com:9002
./tools/test_cli env
- (docker compose up) for start or restart all services, aggregates the output of each container../tools/test_cli env_start
- (docker compose start) start the previously stopped container../tools/test_cli env_stop
- (docker compose stop) stop containers, but won't remove them../tools/test_cli env_down
- (docker compose down) stop containers, and it removes../tools/test_cli env_restart
- (docker compose restart) restart one or more containers
Eureka Server:
It contains a registry of services, and a REST API that can be used to register a service, unregister a service, and determine the location of other services.
Eureka Service:
Any application that can be found in the Eureka Seven service registry and that can be detected by other services. The service has a specific ID, that can refer to one or more instances of the same application.
Eureka Instance:
Any application that is registered on the Eureka Server for discovery by others.
Eureka Client:
Any application that can detect services. It only requests the service registry from Eureka Server to identify running instances of microservices.
By default, the Eureka client starts in the STARTING
state, which allows the instance to perform initialization for a specific application before it can serve traffic.
All operations with Eureka Client may take some time to be reflected on Eureka Server, and then on other Eureka clients. This is due to caching of useful data on the server, which is periodically updated to display new information.
After successful registration, Eureka always declares that the app is in the "UP" state. Change this behavior by enabling Eureka health checks.
eureka:
client:
healthcheck:
enabled: true
Project Reactor is a Java 8 library that implements the reactive programming model. It is based on the reactive stream specification.
!!!Reactive types are not intended to process queries or data faster!!!
Their special feature is their ability to concurrently process more requests and handle delayed operations more efficiently, such as requesting data from a remote server.
To find out the drawbacks of imperative programming look at the implementation between the two components
interface ShoppingCardService{ //(1)
Output calculate(Input value);
}
class OrderService {
private final ShoppingCardService scService;
void process(){ //(2)
Input input = ...;
Output output = scService.calculate(input);//(2.1)
... //(2.2)
}
}
How this code works
In the line 2.1 a synchronous call is made and receiving its result.
The other code is located in the line 2.2.
In this case the services are tightly coupled. Not possible to perform other actions while the ShoppingCardService is busy processing.
calculate()
blocks the thread that runs the OrderService logic
The problem can be solved using callbacks / Hi, callbackhell
interface ShoppingCardService{ //(1)
Output calculate(Input value, Consumer<Output> c);
}
class OrderService {
private final ShoppingCardService scService;
void process(){ //(2)
Input input = ...;
Output output = scService.calculate(input, output -> { //(2.1)
... //(2.2)
});
... //(2.2)
}
}
How this code works
calculate take two arguments and nothing return,
i.e the calling code does not have to wait for a response
Later when the ShoppingCardService sends a response of the callback function, can process (2.2)
class AsyncShoppingCardService implements ShoppingCardService {
public void calculate(Input value, Consumer<Output> c){
new Thread(() -> {
Output result = template.getForObject(...);
...
c.accept(result);
}).start();
}
}
Reactive streams сonsist of 4-Java interfaces publisher, subscriber, subscription and processor
.
public static interface Publisher<T> {
public void subscribe(Subscriber<? super T> subscriber);
} public static interface Subscriber<T> {
public void onSubscribe(Subscription subscription);
public void onNext(T item);
public void onError(Throwable throwable);
public void onComplete();
}
public static interface Subscription {
public void request(long n);
public void cancel();
}
public static interface Processor<T,R> extends Subscriber<T>,
Publisher<R> {
}
They all have the following requirements:
- ASYNC — asynchrony
- NIO - "non-blocking” i / o
- RESPECT BACKPRESSURE - ability to handle cases when data appears faster than it is consumed (this situation does not occur in synchronous, imperative code, but it is common in reactive systems). A mechanism that allows the recipient to ask how much data they want to get. In other words, the recipient starts receiving data only when it is ready to process it.
This is a simple and flexible http client that is natively integrated with Ribbon
and Hystrix
.
Its feature is that we do not need to know where and on what port a service is located.
We just say Feign to the client, go to Wonder-Service
and get all the users from him.
Then Feign refers to Eureka Server
and asks where Wonder-Service
is located.
If Wonder-Service
was registered in Eureka Server
,
then Eureka
will know everything about Wonder-Service
(where it is located, on what port, its URL, etc.).
You only need to describe how to access the remote API service, specifying details such as the URL, request and response body, accepted headers, and so on.
Netflix provides Feign as an abstraction for REST based calls,
For endpoint testing WebFlux
environment comes with the WebTestClient
class.
WebTestClient
is a thin wrapper around WebClient.
You can use it to run queries and check responses.
Is a load balancer.
Out of the box, it is integrated with the Service Discovery mechanism,
which provides a dynamic list of available instances for balancing between them.
Provides:
- Fault tolerance
- Load balancing
- Support for multiple protocols (HTTP, TCP, UDP) in asynchronous and reactive models
- Caching
By default, Spring Cloud Ribbon uses the ZoneAwareLoadBalancer
strategy (Servers in the same zone as our service).
Circuit Breaker pattern.
Library that helps control the interaction between services,
providing fault tolerance and stability to delays,
which increases the stability of the entire system as a whole