# Лабораторная работа №3 - REST API, Gateway и синхронный обмен между микросервисами

## Задание

#### Цель: 

Изучение шаблона проектирования gateway, построения синхронного обмена между микросервисами и архитектурного стиля RESTful API.

#### Задачи:

* Создать 2 микросервиса, реализующих CRUD на связанных сущностях.
* Реализовать механизм синхронного обмена сообщениями между микросервисами.
* Реализовать шлюз на основе прозрачного прокси-сервера nginx.

### Сервисы:
 1. ```product_service``` - сервис, отвечающий за продукты

 2. ```service_service``` - сервис, отвечающий за услуги, которые содержат в себе продукты

#### Связь между сервисами:
 ```service (single) <- product (many)```

## Как запустить программу:
```
docker compose up
```

## Файловая иерархия

```
Лаба 3/
|-- service_service/
|   |-- Dockerfile
|   |-- requirements.txt
|   |-- service_service.py
|-- product_service/
|   |-- Dockerfile
|   |-- requirements.txt
|   |-- product_service.py
|-- nginx/
|   |-- Dockerfile
|   |-- nginx.conf
|-- docker-compose.yml
```

## Описание работы:

Для разработки приложений был выбран язык программирования - ```python```

#### Синхронный обмен сообщениями
`product_service` будет отправлять http-запрос на `service_service` при определенных crud операциях.

#### Операции CRUD для услуги

Просмотр списка имеющихся услуг:
```
@app.route('/all', methods=['GET'])
def show_service_ids():
    return service_ids
```

Добавление услуги:
```
@app.route('/add/<string:name>', methods=['POST'])
def create_service_id(name):
    if request.method == 'POST':
        if len(service_ids) == 0:
            id = 1
        else:
            id = service_ids[-1]['id'] + 1
        service_id = {'id': id, 'name': name}
        service_ids.append(service_id)
        return jsonify(service_id)
```

Поиск услуги по ID:
```
@app.route('/get/<int:id>', methods=['GET'])
def get_service_id(id):
    found_service = next((service_id for service_id in service_ids if service_id['id'] == id), None)
    if found_service:
        return jsonify(found_service)
    else:
        return jsonify({'error': 'service_id not found'})
```

Обновление услуги:
```
@app.route('/upd/<int:id>_<string:name>', methods=['PUT'])
def update_service_id(id, name):
    service = next((service_id for service_id in service_ids if service_id['id'] == id), None)
    if service:
        service['name'] = name
        return jsonify(service)
    else:
        return jsonify({'error': 'service_id not found'})

```

Удаление услуги:
```
@app.route('/del/<int:id>', methods=['DELETE'])
def delete_service_id(id):
    global service_ids
    service_ids = [service_id for service_id in service_ids if service_id['id'] != id]
    return jsonify({'result': True})

if __name__ == '__main__':
    app.run(port=5001)

```

#### Операции CRUD для продукта

Просмотр списка имеющихся продуктов:
```
@app.route('/all', methods=['GET'])
def show_products():
    return products
```

Добавление продукта:
```
@app.route('/add/<int:service_id>_<string:product>', methods=['POST'])
def create_product(service_id, product):
    product = {'id': len(products) + 1,
             'service_id': service_id,
             'product': product,
             'action': 'create'}
    products.append(product)
    requests.post("http://service-service:5001/event", json=product)

    return jsonify(product)
```

Поиск продукта по ID:
```
@app.route('/get/<int:product_id>', methods=['GET'])
def get_product(product_id):
    found_product = next((product for product in products if product['id'] == product_id), None)
    if found_product:
        read_product = {'id': found_product['id'],
                        'service_id': found_product['service_id'],
                        'product': found_product['product']}
        return jsonify(read_product)
    else:
        return jsonify({'error': 'product not found'})
```

Обновление продукта:
```
@app.route('/upd/<int:product_id>/<string:new_product>', methods=['PUT'])
def update_product(product_id, new_product):
    product = next((prod for prod in products if prod['id'] == product_id), None)
    if product:
        product['product'] = new_product
        product['action'] = 'update'
        requests.post("http://service-service:5001/event", json=product)
        return jsonify({'message': 'Product updated successfully'})
    else:
        return jsonify({'error': 'Product not found'})

```

Удаление продукта:
```
@app.route('/product/del_<int:product_id>', methods=['DELETE'])
def delete_product(product_id):
    global products
    deleted_product = next((product for product in products if product['id'] == product_id), None)
    products = [product for product in products if product['id'] != product_id]
    deleted_product['action'] = 'delete'

    requests.post("http://service-service:5001/event", json=deleted_product)

    return jsonify({'result': True})

```

### Docker-compose


Конфигурационный файл ```docker-сompose``` представляет собой многоконтейнерное приложение с тремя сервисами: ```service-service```, ```product-service``` и ```nginx```. Эти сервисы подключены к собственной сети типа "```bridge```" с именем ```my_network```

```
version: '3.8'

services:
  service-service:
    build:
      context: ./service_service
    ports:
      - "5001:5001"
    networks:
      - my_network
    restart: always

  product-service:
    build:
      context: ./product_service
    ports:
      - "5002:5002"
    networks:
      - my_network
    restart: always

  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    depends_on:
      - service-service
      - product-service
    networks:
      - my_network
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
    restart: always

networks:
  my_network:
    driver: bridge
```

### Nginx


Этот файл представляет собой конфигурацию для ```Nginx```, который является веб-сервером и обратным прокси:

```
events {
    worker_connections 1024;
}

http {
    server {
        listen 80;
        listen [::]:80;
        server_name localhost;

        location /service-service/ {
            proxy_pass http://service-service:5001/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /product-service/ {
            proxy_pass http://product-service:5002/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

```
### Dockerfile микросервисов

Dockerfile для услуг
```
FROM python:3.11
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app
CMD ["gunicorn", "--bind", "0.0.0.0:5001", "service_service:app"]
```

Dockerfile для продуктов
```
FROM python:3.11
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app
CMD ["gunicorn", "--bind", "0.0.0.0:5002", "product_service:app"]
```

# Youtube
https://www.youtube.com/watch?v=Sf0CkkcuveY