Технологический стек

Alifshop API построен на современном стеке технологий .NET с акцентом на производительность, масштабируемость и надежность.

Основная платформа

.NET 8.0

  • Версия: .NET 8.0 (LTS)
  • Среда выполнения: ASP.NET Core 8.0
  • Языки: C# 12
  • Модель развертывания: Self-contained deployment

ASP.NET Core

// Конфигурация приложения
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
    .AddNewtonsoftJson();
    
builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options => {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true
        };
    });

var app = builder.Build();

Архитектурные паттерны

CQRS + MediatR

Разделение команд и запросов:

// Команда
public class CreateProductCommand : IRequest<int>
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

// Запрос
public class GetProductQuery : IRequest<ProductDto>
{
    public int ProductId { get; set; }
}

// Регистрация
services.AddMediatR(typeof(CreateProductCommand).Assembly);

Dependency Injection (Autofac)

// Модуль конфигурации
public class CatalogModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<ProductRepository>()
               .As<IProductRepository>()
               .InstancePerLifetimeScope();
               
        builder.RegisterType<ProductService>()
               .As<IProductService>()
               .InstancePerLifetimeScope();
    }
}

Хранение данных

MySQL - Основная база

Версия: MySQL 8.0 ORM: Entity Framework Core 8.0

{
  "ConnectionStrings": {
    "CatalogConnectionString": "Server=localhost;Database=catalog;Uid=user;Pwd=password;",
    "OrdersConnectionString": "Server=localhost;Database=orders;Uid=user;Pwd=password;"
  }
}

Redis - Кеширование

Версия: Redis 7.0 Библиотека: StackExchange.Redis

// Конфигурация Redis
services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
    options.InstanceName = "AlifshopCache";
});

// Использование
public class ProductService
{
    private readonly IDistributedCache _cache;
    
    public async Task<Product> GetProductAsync(int id)
    {
        var cacheKey = $"product_{id}";
        var cachedProduct = await _cache.GetStringAsync(cacheKey);
        
        if (cachedProduct != null)
            return JsonSerializer.Deserialize<Product>(cachedProduct);
            
        var product = await _repository.GetByIdAsync(id);
        await _cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(product), 
            new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
            });
            
        return product;
    }
}

MongoDB - Документы

Версия: MongoDB 7.0 Использование: Корзины, сессии

// Конфигурация MongoDB
services.Configure<MongoDbSettings>(
    configuration.GetSection("MongoDb"));

services.AddSingleton<IMongoClient>(serviceProvider =>
{
    var settings = serviceProvider.GetRequiredService<IOptions<MongoDbSettings>>().Value;
    return new MongoClient(settings.ConnectionString);
});

// Модель документа
public class CartDocument
{
    public ObjectId Id { get; set; }
    public string UserId { get; set; }
    public List<CartItem> Items { get; set; } = new();
    public DateTime CreatedAt { get; set; }
    public DateTime UpdatedAt { get; set; }
}

Elasticsearch - Поиск

Версия: Elasticsearch 7.17 Библиотека: NEST

// Конфигурация Elasticsearch
services.AddSingleton<IElasticClient>(provider =>
{
    var settings = new ConnectionSettings(new Uri("https://localhost:9200"))
        .DefaultIndex("products")
        .BasicAuthentication("elastic", "password")
        .CertificateFingerprint("fingerprint");
        
    return new ElasticClient(settings);
});

// Поиск товаров
public async Task<IEnumerable<Product>> SearchProductsAsync(string query)
{
    var searchResponse = await _elasticClient.SearchAsync<Product>(s => s
        .Query(q => q
            .MultiMatch(m => m
                .Fields(f => f.Field(p => p.Name).Field(p => p.Description))
                .Query(query)
                .Fuzziness(Fuzziness.Auto)
            )
        )
        .Size(50)
    );
    
    return searchResponse.Documents;
}

Интеграции и коммуникации

Event Bus (Kafka)

Версия: Apache Kafka 3.0 Библиотека: KafkaFlow

// Конфигурация Kafka
services.AddKafka(kafka => kafka
    .UseBootstrapServers("localhost:9092")
    .AddCluster(cluster => cluster
        .WithBrokers(new[] { "localhost:9092" })
        .AddConsumer(consumer => consumer
            .Topic("product-events")
            .WithGroupId("catalog-service")
            .WithBufferSize(100)
            .WithWorkersCount(10)
            .AddHandler<ProductEventHandler>()
        )
        .AddProducer<ProductEventProducer>()
    )
);

// Обработка событий
public class ProductEventHandler : IMessageHandler<ProductCreatedEvent>
{
    public async Task Handle(IMessageContext context, ProductCreatedEvent message)
    {
        // Обработка события создания товара
        await _searchService.IndexProductAsync(message.Product);
    }
}

HTTP клиенты

Resilience: Polly для retry/circuit breaker

// Конфигурация HTTP клиентов
services.AddHttpClient<IWarehouseService, WarehouseService>(client =>
{
    client.BaseAddress = new Uri("https://warehouse-service.internal");
    client.DefaultRequestHeaders.Add("Authorization", "Bearer token");
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());

// Политики устойчивости
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .WaitAndRetryAsync(
            retryCount: 3,
            sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
        );
}

Аутентификация и авторизация

JWT Bearer Token

// Конфигурация JWT
services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "service-shop",
            ValidAudience = "http://localhost:8080",
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes("secret-key")
            )
        };
    });

// Политики авторизации
services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => 
        policy.RequireRole("Admin"));
    options.AddPolicy("MerchantAccess", policy => 
        policy.RequireRole("Merchant", "Admin"));
});

Мониторинг и наблюдаемость

Sentry - Отслеживание ошибок

// Конфигурация Sentry
services.AddSentry(options =>
{
    options.Dsn = "https://sentry-dsn";
    options.Debug = false;
    options.TracesSampleRate = 1.0;
});

Serilog - Логирование

// Конфигурация Serilog
Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(configuration)
    .WriteTo.Console()
    .WriteTo.Sentry()
    .CreateLogger();

// Структурированные логи
logger.Information("Product created with {ProductId} for {UserId}", 
    productId, userId);

OpenTelemetry - Метрики

// Конфигурация OpenTelemetry
services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddSource("Alifshop.API")
    )
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddPrometheusExporter()
    );

Дополнительные библиотеки

Валидация - FluentValidation

public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
{
    public CreateProductCommandValidator()
    {
        RuleFor(x => x.Name)
            .NotEmpty()
            .MaximumLength(200);
            
        RuleFor(x => x.Price)
            .GreaterThan(0)
            .LessThan(1000000);
    }
}

Спецификации - Ardalis.Specification

public class ActiveProductsSpecification : Specification<Product>
{
    public ActiveProductsSpecification()
    {
        Query.Where(p => p.IsActive && p.Stock > 0);
    }
}

Задачи - Quartz.NET

// Фоновая задача
public class UpdateInventoryJob : IJob
{
    public async Task Execute(IJobExecutionContext context)
    {
        // Синхронизация остатков
        await _inventoryService.SyncInventoryAsync();
    }
}

// Регистрация задачи
services.AddQuartz(q =>
{
    q.AddJob<UpdateInventoryJob>(opts => opts.WithIdentity("UpdateInventory"));
    q.AddTrigger(opts => opts
        .ForJob("UpdateInventory")
        .WithCronSchedule("0 0 * * * ?") // Каждый час
    );
});

Развертывание

Docker

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
COPY . .
RUN dotnet publish "Alif.ServiceShop.API.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "Alif.ServiceShop.API.dll"]

Docker Compose

version: '3.8'
services:
  app:
    build: .
    ports:
      - "8080:80"
    depends_on:
      - mysql
      - redis
      - kafka
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
      
  redis:
    image: redis:7.0
    
  kafka:
    image: confluentinc/cp-kafka:latest

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