DAS_2023_1/arutunyan_dmitry_lab_3/README.md

250 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Лабораторная работа 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