Пропустити навігацію EPAM

Архітектурні патерни, що працюють: як ми впроваджуємо мікросервіси з .NET 8

Думка експерта
  • .NET

.NET 8 відкриває нові горизонти для впровадження мікросервісної архітектури, яка трансформує традиційні монолітні додатки в гнучкі, незалежні компоненти. Мікросервіси становлять набір автономних, сфокусованих сервісів, що разом формують більший бізнес-додаток. Насамперед ця архітектура дає змогу масштабувати окремі сервіси незалежно один від одного, оптимізуючи використання ресурсів та усуваючи вузькі місця, які можуть впливати на всю програму.

Зараз ми спостерігаємо, як компанії дедалі частіше досягають економії коштів, розв'язують проблеми з розгортанням та покращують DevOps-процеси завдяки використанню контейнерів та мікросервісів. Водночас перехід до архітектурних патернів на основі мікросервісів збільшує масштабованість, гнучкість і підтримуваність програмного забезпечення, хоча й створює певні труднощі. У цій статті ми детально розглянемо, як поступово замінювати монолітні компоненти, щоб з часом перетворити їх на набір мікросервісів з використанням можливостей .NET 8.

Таким чином, наша мета – показати практичний підхід до впровадження мікросервісної архітектури на платформі .NET 8, розглянути основні архітектурні патерни та поділитися досвідом їх застосування у реальних проєктах.

ПРИЄДНУЙСЯ ДО НАШОЇ КОМАНДИ

Основи мікросервісної архітектури в .NET 8

Мікросервісна архітектура в .NET 8 є потужним підходом для створення гнучких і масштабованих додатків. Цей архітектурний стиль передбачає розбиття великого додатка на набір невеликих автономних сервісів, які взаємодіють через мережеві виклики. Кожен сервіс відповідає за окрему бізнес-функцію та може незалежно розгортатися, масштабуватися й оновлюватися.

Переваги мікросервісів у розробці програмного забезпечення

ASP.NET Core, як кросплатформний фреймворк з відкритим вихідним кодом, створений командою Microsoft та активною спільнотою розробників, став одним із найкращих інструментів для побудови мікросервісних систем.

Використання мікросервісної архітектури у поєднанні з ASP.NET Core надає розробникам низку значних переваг:

  • модульність – за конкретні операції відповідають окремі частини додатка, що спрощує розуміння та підтримку коду;
  • надійність – при відмові одного з мікросервісів порушується робота тільки тих функцій, за які він відповідає, не пошкоджуючи програму повністю;
  • змінюваність – можливість змінити один мікросервіс незалежно від інших частин системи;
  • масштабованість – можна будь-якої миті додати або видалити модуль, а при досягненні граничного навантаження розгортати нові екземпляри сервісу в сусідньому кластері;
  • незалежність команд – групи розробників можуть працювати над окремими мікросервісами без прив'язки до інших команд, що пришвидшує реалізацію нових функцій.

Насамперед мікросервіси дозволяють розділити додаток на невеликі компоненти, що зменшує вплив збоїв на всю систему та дає змогу розробникам працювати над окремими частинами паралельно. Це особливо важливо для середніх проєктів і команд, які можуть ефективно координувати роботу. Водночас для великих підприємств із сотнями сервісів мікросервісна архітектура може стати надмірно складною для управління. У таких випадках часто використовують більш монолітні сервіси або гібридні підходи. Крім того, мікросервіси еволюціонують швидко, що призводить до постійного оновлення супутніх сервісів і вимагає ретельного планування архітектури, уникнення центральних точок відмови та впровадження механізмів для забезпечення стабільної взаємодії між компонентами.

Архітектурні патерни: DDD, CQRS, Event Sourcing

Для ефективної реалізації мікросервісної архітектури в .NET 8 широко застосовуються кілька ключових архітектурних патернів:

  1. Domain-Driven Design (DDD) дозволяє структурувати складну бізнес-логіку шляхом моделювання предметної області. У контексті мікросервісів DDD допомагає визначити межі кожного сервісу на основі бізнес-доменів, що забезпечує чітку відповідальність та автономність кожного компонента.
  2. Command Query Responsibility Segregation (CQRS) – це архітектурний патерн, що розділяє операції системи на дві категорії: команди (що змінюють стан) та запити (що повертають дані). CQRS дозволяє оптимізувати обидві сторони незалежно – командну для узгодженості та валідації, а запитову для продуктивності та масштабованості. Основна ідея полягає в тому, що модель для запитів може бути оптимізована окремо від моделі для команд.
  3. Event Sourcing доповнює CQRS, зберігаючи зміни стану як послідовність незмінних подій, а не лише поточний стан. Такий підхід надає повний журнал аудиту, дозволяє відновити будь-який стан системи на певний момент часу та сприяє складній обробці подій. Поєднання CQRS та Event Sourcing дозволяє отримати кілька ключових переваг у додатках .NET 8: незалежне масштабування операцій читання та запису, оптимізацію запитів для конкретних випадків, гнучкість при еволюції системи, та надійність через можливість відтворення подій.

Використання Minimal API для побудови REST-сервісів

У .NET 8 для створення мікросервісів особливо ефективним є використання Minimal API – підходу, що дозволяє будувати HTTP API з мінімальною кількістю залежностей. Цей стиль ідеально підходить для мікросервісів, які повинні містити лише найнеобхідніші файли, функції та залежності.

Minimal API значно спрощують розробку REST-сервісів завдяки меншій кількості шаблонного коду, покращеній читабельності та швидкому розгортанню API без зайвої складності. В .NET 8 ці API стали ще потужнішими завдяки вдосконаленій маршрутизації, впровадженню залежностей, валідації моделей, автентифікації та інтеграції з OpenAPI/Swagger.

Архітектура Minimal API дозволяє створювати API з мінімальною кількістю залежностей, що робить їх ідеальними для мікросервісів. При налаштуванні проєкту використовується підхід, де з самого початку додається тільки необхідний мінімум, а потім функціональність розширюється за потреби. Це забезпечує швидкий запуск, менше коду та оптимізацію для невеликих і середніх REST API в мікросервісній архітектурі.

Налаштування проєкту мікросервісів у .NET 8

Створення мікросервісної архітектури починається з правильного налаштування базового проєкту. .NET 8 надає потужні інструменти командного рядка та шаблони, які спрощують цей процес та дозволяють швидко розгорнути основу для майбутньої системи мікросервісів.

Ініціалізація проєкту: dotnet new web

Першим кроком для побудови мікросервісу є створення нового проєкту. .NET SDK містить команду dotnet new, яка дозволяє створювати проєкти різних типів на основі шаблонів. Для мікросервісів найкраще підходить шаблон веб API, який можна ініціалізувати так:

dotnet new webapi -o MyMicroservice --no-https

Ця команда створює новий проєкт типу webapi у каталозі з назвою MyMicroservice. Параметр -o вказує ім'я каталогу, а флаг --no-https створює додаток, що працюватиме без сертифіката HTTPS, що спрощує розробку на початковому етапі. Після виконання команди можна перейти до створеного каталогу:

cd MyMicroservice

Для інших типів мікросервісів можна використовувати альтернативні шаблони. Наприклад, якщо потрібен більш мінімалістичний підхід, можна створити проєкт на основі шаблону web:

dotnet new web

Цей шаблон створює базовий проєкт, що містить лише необхідний мінімум файлів та залежностей, що ідеально підходить для створення легковажних мікросервісів з використанням Minimal API.

Конфігурація Program.cs для мікросервісу

У .NET 8 файл Program.cs є точкою входу до додатка та місцем, де відбувається вся конфігурація. На відміну від попередніх версій .NET, тепер немає окремого класу Startup.cs — вся конфігурація зосереджена в одному файлі.

Базова структура Program.cs для мікросервісу виглядає так:

var builder = WebApplication.CreateBuilder(args);

// Додавання сервісів до контейнера

builder.Services.AddEndpointsApiExplorer();

builder.Services.AddControllers();

var app = builder.Build();

// Налаштування конвеєра HTTP-запитів

if (app.Environment.IsDevelopment())

{

    app.UseDeveloperExceptionPage();

}

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthorization();

app.MapControllers();

app.Run();

У цьому файлі WebApplication.CreateBuilder(args) створює екземпляр WebApplicationBuilder, який автоматично додає багато служб фреймворку, включно з конфігурацією, логуванням та іншими важливими компонентами. Метод Build() конфігурує хост з набором параметрів за замовчуванням, таких як використання Kestrel як вебсервера, завантаження конфігурації з appsettings.json та налаштування виведення журналів.

Крім того, для мікросервісів часто потрібно налаштувати конвеєр обробки запитів, який складається з компонентів проміжного програмного забезпечення (middleware). Кожен компонент виконує операції в HttpContext і викликає наступний компонент або завершує запит.

Інтеграція Swagger для документації API

Swagger (OpenAPI) — це специфікація для опису REST API, яка дозволяє комп'ютерам і користувачам краще розуміти можливості API без прямого доступу до вихідного коду. Для інтеграції Swagger у мікросервіс, насамперед потрібно додати відповідний пакет:

dotnet add package Swashbuckle.AspNetCore

Після цього необхідно налаштувати сервіси Swagger у файлі Program.cs:

// Додавання Swagger

builder.Services.AddEndpointsApiExplorer();

builder.Services.AddSwaggerGen(c =>

{

    c.SwaggerDoc("v1", new OpenApiInfo

    {

        Title = "Мікросервіс API",

        Version = "v1",

        Description = "API для мікросервісу на .NET 8"

    });

});

А також додати проміжне програмне забезпечення для обслуговування створеного документа JSON та користувацького інтерфейсу Swagger:

if (app.Environment.IsDevelopment())

{

    app.UseSwagger();

    app.UseSwaggerUI();

}

Інтеграція Swagger надає такі переваги для мікросервісної архітектури:

  • автоматична генерація інтерактивної документації api;
  • можливість тестування всіх методів api безпосередньо через веб-інтерфейс;
  • полегшення інтеграції між різними мікросервісами завдяки чітко визначеним контрактам api.

Після запуску додатка документація Swagger буде доступна за адресою https://localhost:<port>/swagger, де можна переглянути та протестувати всі доступні API-ендпоінти мікросервісу, що значно спрощує розробку та інтеграцію.

Реалізація базових мікросервісів: приклади коду

Практичне впровадження мікросервісної архітектури в .NET 8 вимагає розуміння конкретних підходів до створення та інтеграції окремих сервісів. Розглянемо три ключових мікросервіси, що демонструють різні аспекти взаємодії та комунікації.

OrderService: CRUD-операції через REST

Для реалізації базових операцій створення, читання, оновлення та видалення даних (CRUD) у мікросервісі замовлень найкраще підходить REST архітектура. У .NET 8 реалізація REST API стала ще гнучкішою завдяки вдосконаленням у контролерах.

Основою OrderService є клас-контролер, який обробляє HTTP-запити:

public ActionResult<IQueryable<Order>> Get()

{

    return Ok(db.Orders);

}

public ActionResult<Order> Get([FromRoute] int key)

{

    var order = db.Orders.SingleOrDefault(d => d.Id == key);

    if (order == null)

    {

        return NotFound();

    }

    return Ok(order);

}

public ActionResult Post([FromBody] Order order)

{

    db.Orders.Add(order);

    db.SaveChanges();

    return Created(order);

}

Для оновлення даних використовуємо два методи: Put для повного оновлення та Patch для часткового:

public ActionResult Put([FromRoute] int key, [FromBody] Order updatedOrder)

{

    var order = db.Orders.SingleOrDefault(d => d.Id == key);

    if (order == null) return NotFound();

   

    // Оновлення всіх властивостей

    db.SaveChanges();

    return Updated(order);

}

public ActionResult Delete([FromRoute] int key)

{

    var order = db.Orders.SingleOrDefault(d => d.Id == key);

    if (order != null) db.Orders.Remove(order);

    db.SaveChanges();

    return NoContent();

}

DiscountService: gRPC-сервер з Protobuf

Для синхронної міжсервісної комунікації з високою продуктивністю .NET 8 пропонує gRPC – сучасний протокол, що використовує Protocol Buffers для серіалізації даних. Насамперед, для реалізації gRPC-сервера потрібно визначити контракти сервісу.

У DiscountService ми використовуємо підхід code-first, який дозволяє створити gRPC-сервіси без явного визначення .proto файлів:

[DataContract]

public class DiscountModel

{

    [DataMember(Order = 1)]

    public int Id { get; set; }

   

    [DataMember(Order = 2)]

    public string ProductName { get; set; }

   

    [DataMember(Order = 3)]

    public decimal Amount { get; set; }

}

[ServiceContract]

public interface IDiscountService

{

    [OperationContract]

    Task<DiscountModel> GetDiscount(GetDiscountRequest request);

   

    [OperationContract]

    Task<DiscountModel> CreateDiscount(CreateDiscountRequest request);

}

Основні переваги використання gRPC у мікросервісній архітектурі:

  • висока продуктивність та низька затримка;
  • чітко визначені контракти з двобічною типізацією;
  • підтримка потокової передачі даних;
  • міжплатформова сумісність.

BasketService: інтеграція з Redis та RabbitMQ

Для ефективної роботи кошика покупок необхідно забезпечити швидкий доступ до даних і надійну асинхронну комунікацію. Крім того, BasketService демонструє використання кешування з Redis та обміну повідомленнями через RabbitMQ.

Redis використовується як розподілений кеш для зберігання кошиків користувачів, що забезпечує швидкий доступ та масштабованість:

public class CachedBasketRepository : IBasketRepository

{

    private readonly IBasketRepository _repository;

    private readonly IDistributedCache _cache;

   

    public async Task<ShoppingCart> GetBasket(string userName)

    {

        // Спочатку перевіряємо кеш

        var cachedBasket = await _cache.GetStringAsync(userName);

        if (cachedBasket != null) return JsonConvert.DeserializeObject<ShoppingCart>(cachedBasket);

       

        // Якщо не знайдено, звертаємось до репозиторію

        var basket = await _repository.GetBasket(userName);

        await _cache.SetStringAsync(userName, JsonConvert.SerializeObject(basket));

        return basket;

    }

}

Для оформлення замовлення BasketService використовує RabbitMQ – брокер повідомлень, що забезпечує надійну асинхронну комунікацію з OrderService. Однак, при інтеграції MassTransit з RabbitMQ важливо правильно налаштувати обробку подій:

public async Task CheckoutBasket(BasketCheckout basketCheckout)

{

    // Публікація події оформлення замовлення

    await _publishEndpoint.Publish<BasketCheckoutEvent>(new

    {

        UserName = basketCheckout.UserName,

        TotalPrice = basketCheckout.TotalPrice,

        // Інші властивості

    });

   

    // Очищення кошика після оформлення замовлення

    await _repository.DeleteBasket(basketCheckout.UserName);

}

Таким чином, інтеграція різних технологій у мікросервісній архітектурі дозволяє створити ефективну, масштабовану систему, де кожен компонент виконує свою роль, використовуючи найбільш відповідні для нього інструменти.

Патерни продуктивності та масштабування

При побудові мікросервісів надзвичайно важливими є патерни, що забезпечують оптимальну продуктивність та ефективне масштабування системи. Розглянемо чотири ключових підходи, які стали стандартом для високонавантажених додатків на .NET 8.

Асинхронність через async/await

Асинхронне програмування з використанням ключових слів async і await запобігає блокуванню потоків під час виконання операцій введення-виведення. Це особливо важливо для мікросервісів, де затримка одного компонента не повинна впливати на роботу всієї системи.

public async Task<OrderData> GetOrderAsync(int orderId)

{

    var orderDetails = await _repository.GetOrderDetailsAsync(orderId);

    var paymentInfo = await _paymentService.GetPaymentInfoAsync(orderId);

    return new OrderData(orderDetails, paymentInfo);

}

Основні переваги асинхронного підходу:

  • запобігання блокуванню потоків під час мережевих запитів;
  • підвищення загальної відзивчивості системи;
  • можливість одночасного виконання незалежних операцій.

Кешування з Redis для зменшення навантаження

Redis як швидке сховище даних у пам'яті дозволяє зменшити навантаження на базу даних та прискорити відповіді системи, особливо для даних, які часто зчитуються, але рідко змінюються.

public async Task<Product> GetProductAsync(int productId)

{

    string cacheKey = $"product:{productId}";

    var cachedProduct = await _cache.GetStringAsync(cacheKey);

   

    if (cachedProduct != null)

        return JsonConvert.DeserializeObject<Product>(cachedProduct);

   

    var product = await _repository.GetProductAsync(productId);

    await _cache.SetStringAsync(cacheKey, JsonConvert.SerializeObject(product),

        new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) });

   

    return product;

}

Rate Limiting через middleware

Rate Limiting – техніка контролю кількості запитів до API, що забезпечує стабільність та запобігає зловживанню ресурсами. У .NET 8 цей механізм став частиною фреймворку, усуваючи потребу у сторонніх бібліотеках.

builder.Services.AddRateLimiter(options =>

{

    options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>

    {

        return RateLimitPartition.GetFixedWindowLimiter(

            httpContext.User.Identity?.Name ?? httpContext.Connection.RemoteIpAddress?.ToString() ?? "anonymous",

            _ => new FixedWindowRateLimiterOptions { PermitLimit = 100, Window = TimeSpan.FromMinutes(1) });

    });

});

Rate Limiting особливо корисний для:

  • запобігання DoS-атакам;
  • забезпечення справедливого використання ресурсів;
  • уникнення зловживання API.

Горизонтальне масштабування через Kubernetes

Kubernetes забезпечує горизонтальне масштабування мікросервісів, динамічно додаючи або видаляючи екземпляри контейнерів у відповідь на зміни навантаження. Тестування показало, що системи з масштабуванням здатні обробляти вдвічі більше запитів за той самий час порівняно з системами без масштабування.

Основні компоненти автоматичного масштабування в Kubernetes:

  • horizontal pod autoscaler (hpa) для регулювання кількості подів;
  • vertical pod autoscaler (vpa) для зміни ресурсів подів;
  • cluster autoscaler (ca) для зміни кількості вузлів кластера.

Для повідомлень у чергах особливо ефективним є масштабування на основі подій через Kubernetes Event-driven Autoscaling (KEDA), що дозволяє швидко реагувати на зростання кількості повідомлень.

Патерни безпеки для мікросервісів

Безпека є критичним аспектом при розробці мікросервісної архітектури в .NET 8. Застосування відповідних патернів безпеки захищає систему від несанкціонованого доступу та вразливостей.

JWT-автентифікація та рольова авторизація

JWT (JSON Web Token) забезпечує стандарт перевірки автентичності для мікросервісів. На відміну від файлів cookie, JWT є самодостатнім токеном, що інкапсулює дані користувача та може використовуватися між різними сервісами.

Для реалізації у .NET 8 необхідно підключити пакет Microsoft.AspNetCore.Authentication.JwtBearer та налаштувати параметри перевірки:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)

    .AddJwtBearer(options => {

        options.TokenValidationParameters = new TokenValidationParameters {

            ValidateIssuer = true,

            ValidateAudience = true,

            ValidateLifetime = true,

            ValidateIssuerSigningKey = true

        };

    });

Основні переваги JWT-автентифікації:

  • відсутність стану на сервері, що ідеально для мікросервісів;
  • кросплатформенність для різних клієнтів;
  • масштабованість без зберігання сесій;
  • безпека завдяки підпису HMAC або RSA.

Захист API через HTTPS та CORS

Налаштування CORS (Cross-Origin Resource Sharing) є необхідним для безпечної міжсервісної взаємодії. Браузери за замовчуванням блокують запити між доменами згідно з «політикою одного джерела».

Для налаштування CORS у .NET 8:

builder.Services.AddCors(options => {

    options.AddPolicy("AllowSpecificOrigins", policy => {

        policy.WithOrigins("http://example.com")

              .AllowAnyHeader()

              .AllowAnyMethod();

    });

});

Насамперед викликайте app.UseCors() перед app.UseResponseCaching() для правильного порядку проміжного програмного забезпечення.

Валідація вхідних даних через FluentValidation

FluentValidation дозволяє створювати складну логіку перевірки даних. Приклад валідатора:

public class UserValidator : AbstractValidator<User>

{

    public UserValidator()

    {

        RuleFor(user => user.Name).NotEmpty();

        RuleFor(user => user.Email).NotEmpty().EmailAddress();

        RuleFor(user => user.Password).MinimumLength(8);

    }

}

Обробка помилок через UseExceptionHandler

Централізована обробка помилок є важливою для стабільності мікросервісів. UseExceptionHandler перехоплює всі необроблені винятки та дає можливість надіслати відповідну відповідь клієнту:

app.UseExceptionHandler(options => {

    options.Run(async context => {

        context.Response.StatusCode = 500;

        var exceptionFeature = context.Features.Get<IExceptionHandlerFeature>();

        if (exceptionFeature != null)

        {

            await context.Response.WriteAsync("Сталася помилка");

        }

    });

});

Такий підхід дозволяє уникнути витоку конфіденційних даних у разі виникнення помилок та забезпечує єдиний формат відповідей для всієї системи.

Висновок

Отже, використання мікросервісної архітектури з .NET 8 надає розробникам потужний інструментарій для створення гнучких, масштабованих та надійних вебдодатків. Насамперед розглянуті архітектурні патерни такі як Domain-Driven Design, CQRS та Event Sourcing забезпечують структуровану організацію складної бізнес-логіки, що значно полегшує підтримку та розширення системи. Водночас застосування Minimal API дозволяє швидко розробляти легкі REST-сервіси з мінімальною кількістю коду.

Впровадження різноманітних комунікаційних підходів між сервісами забезпечує ефективну взаємодію компонентів системи. Зокрема, REST API ідеально підходить для CRUD-операцій, gRPC забезпечує високопродуктивну синхронну комунікацію, а використання брокерів повідомлень на кшталт RabbitMQ дозволяє реалізувати надійну асинхронну взаємодію.

Безперечно, продуктивність та масштабованість є критично важливими для мікросервісних систем. Саме тому асинхронне програмування через async/await, кешування даних за допомогою Redis, обмеження швидкості через middleware та горизонтальне масштабування через Kubernetes стають невіддільним складником сучасних .NET 8 додатків.

Таким чином, безпека мікросервісів вимагає комплексного підходу, який включає:

  • jwt-автентифікацію та рольову авторизацію;
  • захист api через https та cors;
  • валідацію вхідних даних;
  • централізовану обробку помилок.

Узагальнюючи, мікросервісна архітектура з використанням .NET 8 дозволяє створювати системи, що відповідають сучасним вимогам бізнесу до гнучкості, масштабованості та надійності. Хоча перехід від монолітної до мікросервісної архітектури може бути складним процесом, поступове впровадження окремих сервісів з використанням описаних патернів дає змогу отримати всі переваги цього підходу без суттєвих ризиків. Зрештою, правильно спроєктована мікросервісна система стає потужним фундаментом для подальшого зростання та розвитку програмного забезпечення.

Підписатися на новини

Чудово! Ми вже готуємо добірку актуальних новин для вас :)

Вибачте, щось пішло не так. Будь ласка, спробуйте ще раз.

* Обов'язкові поля

*Будь ласка, заповніть обов’язкові поля