## Лабораторная работа 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 entity = new HttpEntity<>(new HttpHeaders()); ResponseEntity 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