250 lines
15 KiB
Markdown
250 lines
15 KiB
Markdown
|
||
## Лабораторная работа 3. Вариант 4.
|
||
### Задание
|
||
- Создать 2 микросервиса, реализующих CRUD на связанных сущностях.
|
||
- Реализовать механизм синхронного обмена сообщениями между микросервисами.
|
||
- Реализовать шлюз на основе прозрачного прокси-сервера `nginx`.
|
||
Описание предметной области:
|
||
|
||
Микросервисное приложение "Онлайн магазин", состоящее из 4х сервисов:
|
||
- СУБД `PostgreSQL` - используется в качестве хранилища данных приложения, содержит в себе БД с сущностями "заказ" и "товар",
|
||
- Микросервис "Товар" - микросервисное приложение, отвечающее за CRUD сущности "товар",
|
||
- Микросервис "Заказ" - микросервисное приложение, отвечающее за CRUD сущности "заказ" и её связи с сущностью "товар",
|
||
- Прокси-сервер `nginx` - используется в качестве шлюза для переадресации запросов на конкретные микросервисы.
|
||
|
||
### Как запустить
|
||
В директории с файлом характеристик `docker-compose.yaml` выполнить команду:
|
||
```
|
||
docker-compose -f docker-compose.yaml up
|
||
```
|
||
Это запустит `docker-compose`, который развернёт в общем контейнере 4 контейнера с сервисами для работы микросервисного приложения.
|
||
|
||
### Описание работы
|
||
#### Разработка сервиса БД
|
||
Согласно описанию предметной области, БД приложения будет состоять из 2х сущностей, связанных как "Один ко многим":
|
||
```
|
||
order (single) -> product (many)
|
||
```
|
||
В качестве СУБД будет использована PostgreSQL. Для создания таблиц базы данных, создания связей между ними их заполнения тестовыми данными создадим файл sql-запросов `database.sql`:
|
||
```sql
|
||
-- Создание таблицы заказов
|
||
CREATE TABLE t_order (
|
||
id INTEGER PRIMARY KEY UNIQUE NOT NULL,
|
||
status VARCHAR(255) NOT NULL
|
||
);
|
||
-- Создание таблицы товаров
|
||
CREATE TABLE t_product (
|
||
id INTEGER PRIMARY KEY UNIQUE NOT NULL,
|
||
name VARCHAR(255) NOT NULL,
|
||
price INTEGER NOT NULL,
|
||
order_id INTEGER,
|
||
FOREIGN KEY (order_id) REFERENCES t_order(id)
|
||
);
|
||
-- Добавление пустых заказов
|
||
INSERT INTO t_order (id, status)
|
||
VALUES (0, 'Принят'),
|
||
(1, 'В обработке');
|
||
|
||
-- Добавление товаров
|
||
INSERT INTO t_product (id, name, price)
|
||
VALUES (0, 'Гантели', 5000),
|
||
(1, 'Эспандер', 500),
|
||
(2, 'Тренажёр', 25990);
|
||
```
|
||
Для разворачивания сервиса БД в контейнере, пропишем необходимые конфигурации в файле `docker-compose.yaml`:
|
||
```yaml
|
||
db-market-api:
|
||
image: postgres:latest
|
||
ports:
|
||
- 5432:5432
|
||
environment:
|
||
POSTGRES_PASSWORD: admin
|
||
POSTGRES_USER: admin
|
||
POSTGRES_DB: market-api
|
||
volumes:
|
||
- ./database.sql:/docker-entrypoint-initdb.d/database.sql
|
||
restart: always
|
||
```
|
||
Где `image` указывает, что мы берём последний образ `postgres`, `ports` указывает запускать сервис на порту 5432, `environment` определяет название БД - `market-api`, а `volumes` указывает, что при запуске контейнера и вызове функции `initdb`, необходимо использовать созданный ранее файл `database.sql`.
|
||
|
||
#### Разработка микросервиса "Товар"
|
||
Микросервис "Товар" представляет собой `SpringBoot` приложение на языке `Java`. Приложение было разработано в соответствии со стандартом реализации CRUD с использованием `JPA`.
|
||
|
||
На выходе у микросервиса лежит `rest` интерфейс `swagger`, через который можно отправить CRUD запросы приложению.
|
||
|
||
Чтобы настроить связь приложения и БД, создадим конфигурационный файл `application.yaml`:
|
||
```yaml
|
||
server:
|
||
port: "8080"
|
||
spring:
|
||
datasource:
|
||
url: jdbc:postgresql://db-market-api:5432/market-api
|
||
username: admin
|
||
password: admin
|
||
```
|
||
Где `port` указывает, что микросервис будет развёрнут на порту 8080, а `datasource/url` указывает адрес к сервису с БД `market-api`.
|
||
|
||
Чтобы создать образ микросервиса для `docker` контейнера, создадим `Dockerfile`:
|
||
```dockerfile
|
||
FROM openjdk:17-jdk
|
||
|
||
WORKDIR /app
|
||
COPY ./product-service/build/libs/product-service-0.0.1-SNAPSHOT.jar /app/product-service-0.0.1-SNAPSHOT.jar
|
||
EXPOSE 8080
|
||
|
||
CMD ["java", "-jar", "product-service-0.0.1-SNAPSHOT.jar"]
|
||
```
|
||
В которм указывается версия `jdk` для запуска микросервиса, копируется исполняемый файл приложения, указывается порт и обозначается команда при старте контейнера: `java -jar product-service-0.0.1-SNAPSHOT.jar`
|
||
|
||
Чтобы развернуть микросервис в контейнере, пропишем его конфигурации в файле `docker-compose.yaml`:
|
||
```yaml
|
||
product-service:
|
||
build:
|
||
context: .
|
||
dockerfile: ./product-service/Dockerfile
|
||
ports:
|
||
- 8080:8080
|
||
restart: always
|
||
depends_on:
|
||
- db-market-api
|
||
```
|
||
Где `build` указывает на метод сборки образа в котором: `context` указывает на корневую директорию, а `dockerfile` указывает на путь к `Dockerfile`, `ports` устанавливает порт для обращения к микросервису 8080, `depends_on` указывает, что микросервис зависим от БД и разворачивается только после запуска `postgres`.
|
||
|
||
#### Разработка микросервиса "Заказ"
|
||
Микросервис "Заказ" представляет собой `SpringBoot` приложение на языке `Java`. Приложение было разработано в соответствии со стандартом реализации CRUD с использованием `JPA`. В отличие от микросервиса "Товар", данный микросервис отвечает также за связь сущностей товара и заказа, поэтому в соответсвующих методах он отправляет запрос к сервису товаров.
|
||
|
||
Метод добавления товара в заказ:
|
||
```java
|
||
RestTemplate restTemplate = new RestTemplate();
|
||
HttpEntity<String> entity = new HttpEntity<>(new HttpHeaders());
|
||
ResponseEntity<String> response = restTemplate.exchange(
|
||
"http://product-service:8080/product/" + product_id, HttpMethod.GET, entity, String.class);
|
||
|
||
if (response.getStatusCode().is2xxSuccessful()) {
|
||
String responseBody = response.getBody();
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
ProductDto productDto;
|
||
try {
|
||
productDto = objectMapper.readValue(responseBody, ProductDto.class);
|
||
} catch (JsonProcessingException e) {
|
||
throw new Exception("Не удалось десериализовать тело запроса в объект product");
|
||
}
|
||
productDto.setOrder_id(order_id);
|
||
restTemplate.exchange("http://product-service:8080/product/", HttpMethod.PUT, new HttpEntity<>(productDto), String.class);
|
||
} else {
|
||
throw new Exception("Ошибка получения объекта product");
|
||
}
|
||
```
|
||
Таким же способом запросов к микросервису товаров работают методы чтения всех заказов и чтения одного заказа. Этим и достигается микросервисная архитектура приложения.
|
||
|
||
На выходе у микросервиса лежит `rest` интерфейс `swagger`, через который можно отправить CRUD запросы приложению.
|
||
|
||
Чтобы настроить связь приложения и БД, создадим конфигурационный файл `application.yaml`:
|
||
```yaml
|
||
server:
|
||
port: "8081"
|
||
spring:
|
||
datasource:
|
||
url: jdbc:postgresql://db-market-api:5432/market-api
|
||
username: admin
|
||
password: admin
|
||
```
|
||
Где `port` указывает, что микросервис будет развёрнут на порту 8081, а `datasource/url` указывает адрес к сервису с БД `market-api`.
|
||
|
||
Чтобы создать образ микросервиса для `docker` контейнера, создадим `Dockerfile`:
|
||
```dockerfile
|
||
FROM openjdk:17-jdk
|
||
|
||
WORKDIR /app
|
||
COPY ./order-service/build/libs/order-service-0.0.1-SNAPSHOT.jar /app/order-service-0.0.1-SNAPSHOT.jar
|
||
EXPOSE 8081
|
||
|
||
CMD ["java", "-jar", "order-service-0.0.1-SNAPSHOT.jar"]
|
||
```
|
||
В которм указывается версия `jdk` для запуска микросервиса, копируется исполняемый файл приложения, указывается порт и обозначается команда при старте контейнера: `order-service-0.0.1-SNAPSHOT.jar`
|
||
|
||
Чтобы развернуть микросервис в контейнере, пропишем его конфигурации в файле `docker-compose.yaml`:
|
||
```yaml
|
||
order-service:
|
||
build:
|
||
context: .
|
||
dockerfile: ./order-service/Dockerfile
|
||
ports:
|
||
- 8081:8081
|
||
restart: always
|
||
depends_on:
|
||
- db-market-api
|
||
```
|
||
Где `build` указывает на метод сборки образа в котором: `context` указывает на корневую директорию, а `dockerfile` указывает на путь к `Dockerfile`, `ports` устанавливает порт для обращения к микросервису 8081, `depends_on` указывает, что микросервис зависим от БД и разворачивается только после запуска `postgres`.
|
||
|
||
#### Настройка прокси-сервера Nginx
|
||
Согласно заданию, в данной архитектуре `nginx` выполняет работу шлюза и переадресовывает запросы на микросервисы в зависимости от их заголовков. Чтобы это осуществить, пропишем файл конфигураций `nginx`:
|
||
```ini
|
||
events {
|
||
worker_connections 1024;
|
||
}
|
||
|
||
http {
|
||
upstream product-service {
|
||
server product-service:8080;
|
||
}
|
||
upstream order-service {
|
||
server order-service:8081;
|
||
}
|
||
server {
|
||
listen 80;
|
||
listen [::]:80;
|
||
server_name localhost;
|
||
location /product-service/ {
|
||
proxy_pass http://product-service/;
|
||
}
|
||
location /order-service/ {
|
||
proxy_pass http://order-service/;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
Где `events` создаёт событие запуска `nginx`, `upstream product-service` создаёт поток запросов по адресу микросервиса товаров `product-service:8080`, `upstream order-service` создаёт поток запросов по адресу микросервиса заказов `order-service:8080`, `server` разворачивает сервер-шлюз, в котором:
|
||
- `listen` - указывает порт для прослушивания запрсов - 80
|
||
- `server_name` - указывает host сервера, по которому будут проходить запросы - `localhost`
|
||
- `location /product-service/` - указывает префикс запросов к микросервису товаров
|
||
- `proxy_pass http://product-service/` - адресует запросы с данным префиксом к микросервису товаров
|
||
- `location /order-service/` - указывает префикс запросов к микросервису заказов
|
||
- `proxy_pass http://order-service/` - адресует запросы с данным префиксом к микросервису заказов
|
||
|
||
Чтобы развернуть прокси-сервер `nginx` в контейнере, пропишем его конфигурации в файле `docker-compose.yaml`:
|
||
```yaml
|
||
nginx:
|
||
image: nginx:latest
|
||
ports:
|
||
- 80:80
|
||
volumes:
|
||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||
restart: always
|
||
depends_on:
|
||
- product-service
|
||
- order-service
|
||
```
|
||
Где `image` указывает, что мы берём последний образ `nginx`, `ports` указывает запускать сервер на порту 80, `volumes` указывает, что при запуске контейнера, локальный файл конфигураций `nginx.conf` следует поместить вместо стандартного файла конфигураций `nginx`, а depends_on указывает, что данный прокси-сервер зависим от обеих микросервисов и разворачивается только после их запуска.
|
||
|
||
> **Note**
|
||
>
|
||
> Для обеспечения работы прокси-сервера `nginx` в качестве шлюза, необходимо чтобы все управляемые им сервисы находились в одной сети типа "мост". Если это не так, то в файле `docker-compose.yaml` необходимо создать виртуальную сеть между контейнерами
|
||
```yaml
|
||
networks:
|
||
mynetwork:
|
||
driver: bridge
|
||
```
|
||
> и назначить её каждому из контейнеров.
|
||
|
||
#### Разворачивание микросервисного приложения
|
||
log-журнал контейнеров:
|
||
![](pic2.png "")
|
||
|
||
Пример запроса к микросервисному приложению:
|
||
![](pic1.png "")
|
||
|
||
Примеры остальных запросов показаны в видео.
|
||
|
||
### Видео
|
||
https://youtu.be/VxaZRfyu3pI |