Skip to main content

Overview

The Discount service is a gRPC-based microservice that manages discount coupons for products in the e-commerce system. Unlike the other services that use PostgreSQL or MongoDB, this service uses SQLite for lightweight data storage with Entity Framework Core.

Architecture

  • Protocol: gRPC (HTTP/2)
  • Framework: ASP.NET Core 8.0
  • Database: SQLite
  • ORM: Entity Framework Core 8.0
  • Mapper: Mapster
  • Location: src/Services/Discount/Discount.Grpc/

Key Features

gRPC Communication

  • High-performance binary protocol using Protocol Buffers
  • Strongly-typed service contracts
  • Efficient inter-service communication
  • Used by Basket service for real-time discount calculations

CRUD Operations

  • GetDiscount: Retrieve discount by product name
  • CreateDiscount: Create new discount coupon
  • UpdateDiscount: Update existing discount
  • DeleteDiscount: Remove discount coupon

SQLite Database

  • Lightweight, serverless database
  • File-based storage (discountdb)
  • Entity Framework Core migrations
  • Pre-seeded with sample data

Project Structure

Discount.Grpc/
├── Data/
│   ├── DiscountContext.cs      # EF Core DbContext
│   └── Extentions.cs           # Migration extensions
├── Migrations/                  # EF Core migrations
├── Models/
│   └── Coupon.cs               # Coupon entity
├── Protos/
│   └── discount.proto          # gRPC service definition
├── Services/
│   └── DiscountService.cs      # gRPC service implementation
└── Program.cs                   # Application startup

Service Configuration

Program.cs Setup

using Discount.Grpc.Data;
using Discount.Grpc.Services;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add gRPC services
builder.Services.AddGrpc();

// Configure SQLite with Entity Framework Core
builder.Services.AddDbContext<DiscountContext>(opts =>
    opts.UseSqlite(builder.Configuration.GetConnectionString("Database")));

var app = builder.Build();

// Apply migrations and seed data
app.UseMigration();

// Map gRPC service
app.MapGrpcService<DiscountService>();

app.Run();

appsettings.json

{
  "ConnectionStrings": {
    "Database": "Data Source=discountdb"
  },
  "Kestrel": {
    "EndpointDefaults": {
      "Protocols": "Http2"
    }
  }
}
The Kestrel configuration is crucial - it sets HTTP/2 as the protocol, which is required for gRPC communication.

Database Model

Coupon Entity

namespace Discount.Grpc.Models;

public class Coupon
{
    public int Id { get; set; }
    public string ProductName { get; set; } = default!;
    public string Description { get; set; } = default!;
    public int Amount { get; set; }
}

DbContext

using Discount.Grpc.Models;
using Microsoft.EntityFrameworkCore;

namespace Discount.Grpc.Data;

public class DiscountContext : DbContext
{
    public DbSet<Coupon> Coupons { get; set; } = default!;

    public DiscountContext(DbContextOptions<DiscountContext> options)
       : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Seed sample data
        modelBuilder.Entity<Coupon>().HasData(
            new Coupon { Id = 1, ProductName = "IPhone X", Description = "IPhone Discount", Amount = 150 },
            new Coupon { Id = 2, ProductName = "Samsung 10", Description = "Samsung Discount", Amount = 100 }
        );
    }
}

Migration Extension

using Microsoft.EntityFrameworkCore;

namespace Discount.Grpc.Data;

public static class Extentions
{
    public static IApplicationBuilder UseMigration(this IApplicationBuilder app)
    {
        using var scope = app.ApplicationServices.CreateScope();
        using var dbContext = scope.ServiceProvider.GetRequiredService<DiscountContext>();
        dbContext.Database.MigrateAsync();

        return app;
    }
}

NuGet Dependencies

<PackageReference Include="Grpc.AspNetCore" Version="2.60.0" />
<PackageReference Include="Mapster" Version="7.4.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.1" />

Integration with Other Services

Basket Service Integration

The Basket service consumes the Discount gRPC service to apply discounts when storing shopping carts: Client Configuration (in Basket.API/Program.cs):
builder.Services.AddGrpcClient<DiscountProtoService.DiscountProtoServiceClient>(options =>
{
    options.Address = new Uri(builder.Configuration["GrpcSettings:DiscountUrl"]!);
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
    var handler = new HttpClientHandler
    {
        ServerCertificateCustomValidationCallback =
        HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
    };
    return handler;
});
Usage Example (in StoreBasketHandler):
private async Task DeductDiscount(ShoppingCart cart, CancellationToken cancellationToken)
{
    // Communicate with Discount.Grpc and calculate latest prices
    foreach (var item in cart.Items)
    {
        var coupon = await discountProto.GetDiscountAsync(
            new GetDiscountRequest { ProductName = item.ProductName },
            cancellationToken: cancellationToken);
        
        item.Price -= coupon.Amount;
    }
}

Why gRPC?

  1. Performance: Binary serialization is faster than JSON
  2. Type Safety: Strongly-typed contracts prevent runtime errors
  3. HTTP/2: Multiplexing, header compression, and bidirectional streaming
  4. Contract-First: Proto files define clear service contracts
  5. Cross-Platform: Works across different languages and platforms

Why SQLite?

  1. Lightweight: No separate database server required
  2. Simple Deployment: Single file database
  3. Sufficient for Discounts: Read-heavy workload with low write volume
  4. EF Core Support: Full Entity Framework Core compatibility
  5. Development-Friendly: Easy setup and testing

Running the Service

Standalone

cd src/Services/Discount/Discount.Grpc
dotnet run
The service runs on HTTP/2 (gRPC default port).

With Docker

docker-compose up discount.grpc

Next Steps

Build docs developers (and LLMs) love