DAS_2023_1/arutunyan_dmitry_lab_3/README.md

250 lines
15 KiB
Markdown
Raw Normal View History

2023-11-12 20:59:21 +04:00
## Лабораторная работа 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