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
|