Skip to main content

Basket Service Overview

The Basket service manages shopping cart operations for the e-commerce platform. It provides high-performance cart management with Redis caching, discount calculation via gRPC communication, and event-driven checkout integration.

Architecture

The Basket service is built using:
  • ASP.NET Core Minimal APIs with Carter for endpoint routing
  • MediatR with CQRS pattern for command/query separation
  • Marten for PostgreSQL document storage
  • Redis for distributed caching with decorator pattern
  • gRPC Client for synchronous communication with Discount service
  • MassTransit for asynchronous messaging via RabbitMQ

Key Components

Domain Models

ShoppingCart

The main aggregate representing a user’s shopping cart:
public class ShoppingCart
{
    public string UserName { get; set; } = default!;
    public List<ShoppingCartItem> Items { get; set; } = new();
    public decimal TotalPrice => Items.Sum(x => x.Price * x.Quantity);

    public ShoppingCart(string userName)
    {
        UserName = userName;
    }
}

ShoppingCartItem

Individual items within the shopping cart:
public class ShoppingCartItem
{
    public int Quantity { get; set; } = default!;
    public string Color { get; set; } = default!;
    public decimal Price { get; set; } = default!;
    public Guid ProductId { get; set; } = default!;
    public string ProductName { get; set; } = default!;
}

Data Persistence

The service uses a dual-layer persistence strategy:
  1. Primary Storage: PostgreSQL via Marten document database
  2. Cache Layer: Redis distributed cache for performance

Repository Pattern

public interface IBasketRepository
{
    Task<ShoppingCart> GetBasket(string userName, CancellationToken cancellationToken = default);
    Task<ShoppingCart> StoreBasket(ShoppingCart basket, CancellationToken cancellationToken = default);
    Task<bool> DeleteBasket(string userName, CancellationToken cancellationToken = default);
}

Service Configuration

The service is configured in Program.cs with the following dependencies:
// Data Services
builder.Services.AddMarten(opts =>
{
    opts.Connection(builder.Configuration.GetConnectionString("Database")!);
    opts.Schema.For<ShoppingCart>().Identity(x => x.UserName);
}).UseLightweightSessions();

builder.Services.AddScoped<IBasketRepository, BasketRepository>();
builder.Services.Decorate<IBasketRepository, CachedBasketRepository>();

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
});

// Grpc Services
builder.Services.AddGrpcClient<DiscountProtoService.DiscountProtoServiceClient>(options =>
{
    options.Address = new Uri(builder.Configuration["GrpcSettings:DiscountUrl"]!);
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
    var handler = new HttpClientHandler
    {
        ServerCertificateCustomValidationCallback =
        HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
    };
    return handler;
});

// Async Communication Services
builder.Services.AddMessageBroker(builder.Configuration);

Application Settings

{
  "ConnectionStrings": {
    "Database": "Server=localhost;Port=5433;Database=BasketDb;User Id=postgres;Password=postgres;Include Error Detail=true",
    "Redis": "localhost:6379"
  },
  "GrpcSettings": {
    "DiscountUrl": "https://localhost:5052"
  },
  "MessageBroker": {
    "Host": "amqp://localhost:5672",
    "UserName": "guest",
    "Password": "guest"
  }
}

Health Checks

The service includes health checks for its dependencies:
builder.Services.AddHealthChecks()
    .AddNpgSql(builder.Configuration.GetConnectionString("Database")!)
    .AddRedis(builder.Configuration.GetConnectionString("Redis")!);

app.UseHealthChecks("/health",
    new HealthCheckOptions
    {
        ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
    });

Exception Handling

Custom exception for basket not found scenarios:
public class BasketNotFoundException : NotFoundException
{
    public BasketNotFoundException(string userName) : base("Basket", userName)
    {
    }
}

Cross-Cutting Concerns

The service implements several cross-cutting concerns via MediatR behaviors:
  • Validation: FluentValidation integration for request validation
  • Logging: Structured logging for all operations
  • Exception Handling: Global exception handler for consistent error responses
builder.Services.AddMediatR(config =>
{
    config.RegisterServicesFromAssembly(assembly);
    config.AddOpenBehavior(typeof(ValidationBehavior<,>));
    config.AddOpenBehavior(typeof(LoggingBehavior<,>));
});

Communication Patterns

Synchronous Communication

  • gRPC: Communicates with Discount service to retrieve and apply coupon discounts

Asynchronous Communication

  • Event Publishing: Publishes BasketCheckoutEvent to RabbitMQ via MassTransit when checkout occurs

Next Steps

Build docs developers (and LLMs) love