This commit is contained in:
Vladislav Moiseev 2023-10-25 23:53:03 +04:00
parent eb966f9a4e
commit 75b9b7df84
5 changed files with 251 additions and 1 deletions

View File

@ -7,7 +7,7 @@
1. [Знакомство с docker и docker-compose](labs/lab_1/README.md) 1. [Знакомство с docker и docker-compose](labs/lab_1/README.md)
2. [Разработка простейшего распределённого приложения](labs/lab_2/README.md) 2. [Разработка простейшего распределённого приложения](labs/lab_2/README.md)
3. TBA 3. [REST API, Gateway и синхронный обмен между микросервисами](labs/lab_3/README.md)
4. TBA 4. TBA
5. TBA 5. TBA
6. TBA 6. TBA

127
labs/lab_3/README.md Normal file
View File

@ -0,0 +1,127 @@
# Лабораторная работа №3 - REST API, Gateway и синхронный обмен между микросервисами
Цель: изучение шаблона проектирования gateway, построения синхронного обмена между микросервисами и архитектурного стиля RESTful API.
Задачи:
1. Создать 2 микросервиса, реализующих CRUD на связанных сущностях.
2. Реализовать механизм синхронного обмена сообщениями между микросервисами.
3. Реализовать шлюз на основе прозрачного прокси-сервера nginx.
## Создание микросервисов
Создать два микросервиса.
Каждый сервис реализует CRUD-операции: список записей, подробности конкретной записи, создание, удаление и изменение записи.
В качестве хранилища данных может выступать оперативная память приложения или база данных.
Сущности необходимо подобрать по следующим критериям:
1. Они должны быть связаны с предполагаемой темой диплома.
2. Они должны быть связаны как `1-ко-многим``.
> Например, одна сущность - книга в библиотеке, вторая - абонемент.
>
> Поля абонемента: УИД (уникальный идентификатор) абонемента, Номер, ФИО читателя, Дата выдачи.
> Поля книги: УИД книги, Автор, Название, Год издания, УИД абонемента, на котором сейчас находится книга.
Общение между сервисами должно происходить при помощи API, реализованного при помощи архитектурного стиля проектирования REST.
То есть необходимо реализовать следующие endpoints:
1. "GET /" - Получение списка записей.
Должен возвращать массив моделей.
2. "GET /{uuid}" - Получение подробностей записи по УИД (uuid).
Должен возвращать модель или ошибку 404.
3. "POST /" - Создание новой записи.
Принимает на вход данные. Должен возвращать модель из п.2.
4. "PUT /{uuid}" - Обновление записи по УИД.
Принимает на вход новые данные. Должен возвращать модель из п.2 или ошибку 404.
5. "DELETE /{uuid}" - Удаление записи по УИД.
Возвращает коды 200 при успехе и 404, если запись не найдена.
Пример модели абонемента для списка и подробностей:
```json
{
"uuid": "8f036445-a5bd-401c-926e-840f9de795cd",
"number": 135,
"fullName": "Иванов И.И.",
"issued": "2023-10-18T05:41:00Z"
}
```
Пример модели абонемента для создания или изменения:
```json
{
"number": 135,
"fullName": "Иванов И.И.",
"issued": "2023-10-18T05:41:00Z"
}
```
> Как видите, нет УИДа, т.к. он присваивается системой, и изменять мы его не можем.
Пример модели книги для списка:
```json
{
"uuid": "8740d660-b251-4272-8535-be7ec3748d4b",
"author": "J.K.R.",
"subject": "HP and PS",
"year": 1997,
"subscriptionUuid": "8f036445-a5bd-401c-926e-840f9de795cd"
}
```
Пример модели книги для подробностей:
```json
{
"uuid": "8740d660-b251-4272-8535-be7ec3748d4b",
"author": "J.K.R.",
"subject": "HP and PS",
"year": 1997,
"subscriptionUuid": "8f036445-a5bd-401c-926e-840f9de795cd",
"subscriptionInfo": {
"number": 135,
"fullName": "Иванов И.И.",
"issued": "2023-10-18T05:41:00Z"
}
}
```
> Как видите, модель сильно богаче, чем при запросе списка.
>
> Как раз здесь и нужен синхронный обмен между сервисами.
Пример модели книги для создания или изменения:
```json
{
"author": "J.K.R.",
"subject": "HP and PS",
"year": 1997,
"subscriptionUuid": "8f036445-a5bd-401c-926e-840f9de795cd"
}
```
Как понять, куда обращаться микросервису книг для получения данных об абонементах?
Нам здесь поможет docker compose: код в "service" совпадает с хостом, куда следует обращаться.
## Реализация синхронного обмена
Как реализовать непосредственно работу с endpoints?
Например, использовать ASP.NET Core Minimal APIs.
[Небольшой пример, который это показывает](./example_1.cs).
Как вызвать синхронно данные с соседнего микросервиса?
Через HTTP-клиент!
[Небольшой пример, который это показывает](./example_2.cs).
## Реализация gateway при помощи nginx
Один сервер, несколько location, proxy_pass по хосту из docker-compose.yml, открытые порты наружу и... всё.
[Пример файла с настройкой nginx](./example_nginx.conf).

76
labs/lab_3/example_1.cs Normal file
View File

@ -0,0 +1,76 @@
List<TaskEntity> tasks = new()
{
new TaskEntity() { Uuid= Guid.NewGuid(), Subject = "Сдать третью лабу", IsCompleted = false },
};
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Массив сущностей.
app.MapGet("/", () =>
{
return tasks.Select(t => new TaskEntityDto()
{
Uuid = t.Uuid,
Subject = t.Subject,
IsCompleted = t.IsCompleted,
});
});
// Подробности сущности.
app.MapGet("/{uuid}", (Guid uuid) =>
{
var task = tasks.FirstOrDefault(t => t.Uuid == uuid);
if (task == null)
return Results.NotFound();
return Results.Json(new TaskEntityDto()
{
Uuid = task.Uuid,
Subject = task.Subject,
IsCompleted = task.IsCompleted,
});
});
// Создание сущности.
app.MapPost("/", () => { throw new NotImplementedException(); });
// Изменение сущности.
app.MapPut("/{uuid}", () => { throw new NotImplementedException(); });
// Удаление сущности.
app.MapPost("/", () => { throw new NotImplementedException(); });
app.Run();
/// <summary>
/// Сама сущность.
/// </summary>
public class TaskEntity
{
public Guid Uuid { get; set; }
public string Subject { get; set; } = string.Empty;
public bool IsCompleted { get; set; }
}
/// <summary>
/// DTO-класс для описания сущности в API.
/// </summary>
public class TaskEntityDto : TaskEntity { }
/// <summary>
/// Запрос на создание сущности.
/// </summary>
public class TaskCreateRequest
{
public string Subject { get; set; } = string.Empty;
public bool IsCompleted { get; set; }
}
/// <summary>
/// Запрос на изменение сущности.
/// </summary>
public class TaskEditRequest
{
public string Subject { get; set; } = string.Empty;
public bool IsCompleted { get; set; }
}

26
labs/lab_3/example_2.cs Normal file
View File

@ -0,0 +1,26 @@
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient(); // Добавляем библиотеку по работе с HTTP.
var app = builder.Build();
// Да, по заданию надо будет запрашивать метод /{uuid}. Тут запрашивается список.
app.MapGet("/", async (IHttpClientFactory httpClientFactory) =>
{
var remoteHost = "http://localhost:5158"; // Адрес, по которому развёрнут микросервис с задачами. В docker compose тут будет имя service.
var client = httpClientFactory.CreateClient();
var response = await client.GetFromJsonAsync<List<TaskEntityDto>>(remoteHost);
if (response == null)
return Results.BadRequest();
return Results.Ok($"Соседний микросервис отдал следующие задачи: {string.Join(", ", response.Select(i => i.Subject))}");
});
app.Run();
/// <summary>
/// DTO-класс для описания сущности из другого микросервиса.
/// </summary>
public class TaskEntityDto
{
public Guid Uuid { get; set; }
public string Subject { get; set; } = string.Empty;
public bool IsCompleted { get; set; }
}

View File

@ -0,0 +1,21 @@
server {
listen 80;
listen [::]:80;
server_name localhost;
location /test/ {
proxy_pass http://test:80/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Prefix /test;
}
location /admin/ {
proxy_pass http://admin:80/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Prefix /admin;
}
}