A .NET 8 e-commerce microservices solution. Catalog, Basket, Discount, and Ordering services are connected via Redis cache, gRPC, RabbitMQ (MassTransit), and a YARP API Gateway.
- Architecture Overview
- Services
- Ports and Connections
- Project Structure
- Patterns Used
- Technology Stack
- Prerequisites
- Quick Start
- Local Development
- Docker Compose
- API Gateway (YARP)
- Service Details
- Sample Requests
- Checkout Flow
- Health Checks
- Troubleshooting
┌─────────────────────┐
│ Client / Swagger │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ YARP API Gateway │
│ :6005 │
└──────────┬──────────┘
┌─────────────────────┼─────────────────────┐
│ │ │
/catalog-service /basket-service /ordering-service
│ │ │
┌────────▼────────┐ ┌────────▼────────┐ ┌────────▼────────┐
│ Catalog.API │ │ Basket.API │ │ Ordering.API │
│ :6001 │ │ :6002 │ │ :6004 │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
PostgreSQL ┌────┴────┐ SQL Server
(catalogdb) │ │ (orderdb)
│ gRPC │ ▲
▼ ▼ │
PostgreSQL Discount.Grpc RabbitMQ
+ Redis :6003 (MassTransit)
(basketdb) (SQLite) messagebroker
| Type | Where | When |
|---|---|---|
| REST (HTTP) | Client ↔ Catalog / Basket / Ordering | CRUD and queries |
| gRPC | Basket → Discount | Apply discounts when storing basket items |
| Async messaging | Basket → RabbitMQ → Ordering | Create orders after checkout |
| YARP proxy | Client → Gateway → services | Single entry point |
| Service | Type | Port | Data store | Description |
|---|---|---|---|---|
| Catalog.API | REST | 6001 | PostgreSQL (Marten) | Product catalog |
| Basket.API | REST | 6002 | PostgreSQL + Redis | Shopping cart |
| Discount.Grpc | gRPC | 6003 | SQLite | Product-based discounts |
| Ordering.API | REST | 6004 | SQL Server | Order management (Clean Architecture) |
| YarpApiGateway | Reverse Proxy | 6005 | — | API Gateway / routing |
| Container | Image | Port | Purpose |
|---|---|---|---|
catalogdb |
postgres:16-alpine | 5432 | Catalog DB |
basketdb |
postgres:16-alpine | 5433 | Basket DB |
distributedcache |
redis:7-alpine | 6379 | Basket cache |
orderdb |
mssql/server:2022 | 1433 | Ordering DB |
messagebroker |
rabbitmq:3-management | 5672 / 15672 (UI) | Event bus |
| Source | Target | Address / notes |
|---|---|---|
| Basket (local) | Discount gRPC | http://localhost:6003 |
| Basket (Docker) | Discount gRPC | http://discount.grpc:8080 |
| Basket / Ordering | RabbitMQ | amqp://localhost:5672 (local) |
| Gateway (local) | Catalog / Basket / Ordering | localhost:6001 / 6002 / 6004 |
| Gateway (Docker) | Services | http://*.api:8080 |
Default credentials (development):
- Postgres:
postgres/postgres - SQL Server:
sa/SwN12345678 - RabbitMQ:
guest/guest— UI: http://localhost:15672
EShopMicroservices/
├── docker-compose.yml
├── docker-compose.override.yml
├── global.json # SDK 8.x
└── src/
├── eshop-microservices.sln
├── BuildingBlocks/
│ ├── BuildingBlocks.csproj # CQRS, behaviors, exceptions, Carter, MediatR…
│ ├── Behaviors/ # ValidationBehavior, LoggingBehavior
│ ├── CQRS/
│ ├── Exceptions/
│ ├── Pagination/
│ └── BuildingBlocks.Messaging/ # MassTransit + BasketCheckoutEvent
├── ApiGateways/
│ └── YarpApiGateway/ # Reverse proxy + rate limiting
└── Services/
├── Catalog/
│ └── Catalog.API/ # Vertical slice + Marten
├── Basket/
│ └── Basket.API/ # Repository + Redis decorator + gRPC client
├── Discount/
│ └── Discount.Grpc/ # gRPC server + EF Core SQLite
└── Ordering/
├── Ordering.API/
├── Ordering.Application/ # CQRS + BasketCheckoutEventHandler
├── Ordering.Domain/ # Aggregates, value objects, domain events
└── Ordering.Infrastructure/ # EF Core SQL Server
| Pattern | Where |
|---|---|
| CQRS + MediatR | Catalog, Basket, Ordering |
| Vertical Slice | Catalog, Basket feature folders |
| Clean Architecture | Ordering (Domain / Application / Infrastructure / API) |
| Repository | Basket (IBasketRepository) |
| Decorator + Cache-aside | CachedBasketRepository + Redis |
| gRPC | Discount server, Basket client |
| Pub/Sub (async) | Basket checkout → RabbitMQ → Ordering |
| API Gateway / Routing | YARP |
| Rate limiting | Gateway → ordering-service |
| Exception handling | CustomExceptionHandler (BuildingBlocks) |
- .NET 8 / ASP.NET Core
- Carter — minimal API endpoint modules
- MediatR — command/query pipeline
- FluentValidation — request validation
- Marten — PostgreSQL document DB (Catalog, Basket)
- EF Core — SQLite (Discount), SQL Server (Ordering)
- StackExchange.Redis — distributed cache
- gRPC (
Grpc.AspNetCore) - MassTransit + RabbitMQ
- YARP (
Yarp.ReverseProxy) - Mapster — object mapping
- Swagger / OpenAPI
- Docker Compose
- .NET 8 SDK (compatible with
global.json) - Docker Desktop (databases, Redis, RabbitMQ, optional full stack)
- (Optional) Visual Studio 2022 / Rider / VS Code + C# Dev Kit
dotnet --version # should be 8.x
docker --versioncd EShopMicroservices
docker compose up -d catalogdb basketdb distributedcache orderdb messagebrokerdotnet run --project src/Services/Catalog/Catalog.API
dotnet run --project src/Services/Basket/Basket.API
dotnet run --project src/Services/Discount/Discount.Grpc
dotnet run --project src/Services/Ordering/Ordering.API
dotnet run --project src/ApiGateways/YarpApiGateway| Service | URL |
|---|---|
| Catalog Swagger | http://localhost:6001/swagger |
| Basket Swagger | http://localhost:6002/swagger |
| Ordering Swagger | http://localhost:6004/swagger |
| API Gateway | http://localhost:6005 |
| RabbitMQ Management | http://localhost:15672 |
Build the solution:
dotnet build src/eshop-microservices.slnYou can keep only infrastructure in Docker and run APIs with dotnet watch run from the IDE.
Basket (appsettings.json):
"ConnectionStrings": {
"Database": "Host=localhost;Port=5433;Database=BasketDb;Username=postgres;Password=postgres",
"Redis": "localhost:6379"
},
"GrpcSettings": {
"DiscountUrl": "http://localhost:6003"
},
"MessageBroker": {
"Host": "amqp://localhost:5672",
"UserName": "guest",
"Password": "guest"
}Discount: SQLite file Data Source=discountdb (created under the project folder).
Ordering: SQL Server localhost,1433 / OrderDb / sa / SwN12345678.
Full stack:
docker compose up -d --buildSpecific services only:
docker compose up -d basketdb distributedcache discount.grpc basket.apiLogs:
docker compose logs -f basket.api
docker compose logs -f ordering.apiStop:
docker compose down
# Also remove volumes:
docker compose down -vSingle entry point: http://localhost:6005
| Gateway path | Backend |
|---|---|
/catalog-service/{**catch-all} |
Catalog.API |
/basket-service/{**catch-all} |
Basket.API |
/ordering-service/{**catch-all} |
Ordering.API |
Path transform example:
GET http://localhost:6005/basket-service/basket/swn
→ GET http://localhost:6002/basket/swn
The ordering route uses a fixed window rate limit: max 5 requests per 10 seconds.
Discount gRPC is not exposed through the gateway; Basket calls it directly over gRPC.
- Marten document store + PostgreSQL
- CQRS (Create / Get / Update / Delete products)
- Seed data (development)
- Get / Store / Delete / Checkout basket
BasketRepository(Marten) +CachedBasketRepository(Redis decorator, cache-aside)- Applies discounts via Discount gRPC on store
- On checkout: BasketCheckoutEvent → RabbitMQ (MassTransit
IPublishEndpoint)
- Proto:
GetDiscount,CreateDiscount,UpdateDiscount,DeleteDiscount - EF Core + SQLite + migrations
- Seed:
IPhone X(−150),Samsung 10(−100) - HTTP/2 only (requires a gRPC client; browser/curl HTTP/1.1 will fail)
- Domain:
Orderaggregate, value objects, domain events - Application: Commands/Queries +
BasketCheckoutEventHandler(IConsumer<>) - Infrastructure: EF Core SQL Server, interceptors, migrations
- API: Carter endpoints + Swagger
When a checkout event arrives, CreateOrderCommand runs and the order is persisted to OrderDb.
curl -X POST http://localhost:6005/basket-service/basket \
-H "Content-Type: application/json" \
-d '{
"cart": {
"userName": "swn",
"items": [
{
"quantity": 1,
"color": "Black",
"price": 950,
"productId": "5334c996-8457-4cf0-815c-ed2b77c4ff61",
"productName": "IPhone X"
}
]
}
}'Discount applies −150 for IPhone X → stored price becomes 800.
curl http://localhost:6005/basket-service/basket/swncurl -X POST http://localhost:6005/basket-service/basket/checkout \
-H "Content-Type: application/json" \
-d '{
"basketCheckoutDto": {
"userName": "swn",
"customerId": "58c0eec7-3df2-4d8e-9b8f-8f0f0f0f0f0f",
"firstName": "John",
"lastName": "Doe",
"emailAddress": "john@test.com",
"addressLine": "Street 1",
"country": "TR",
"state": "Istanbul",
"zipCode": "34000",
"cardName": "John Doe",
"cardNumber": "1234567890123456",
"expiration": "12/28",
"cvv": "123",
"paymentMethod": 1
}
}'You should then see BasketCheckoutEvent / order creation in Ordering logs, and related queue activity in the RabbitMQ UI.
curl http://localhost:6005/ordering-service/orders1. POST /basket/checkout
2. Basket: load cart, set TotalPrice
3. Basket: Publish(BasketCheckoutEvent) → RabbitMQ
4. Basket: delete cart, return 200
5. Ordering: BasketCheckoutEventHandler.Consume
6. Ordering: CreateOrderCommand → SQL Server
Basket does not wait for Ordering to be available (async / eventual consistency).
| Service | Endpoint |
|---|---|
| Catalog | http://localhost:6001/health |
| Basket | http://localhost:6002/health |
| Ordering | http://localhost:6004/health |
Basket health checks validate the PostgreSQL (NpgSql) connection.
basketdb is not running:
docker compose up -d basketdbdocker compose up -d distributedcacheStart Discount (:6003). Verify Basket GrpcSettings:DiscountUrl.
- Is RabbitMQ up? http://localhost:15672
- Is Ordering.API running?
- Are
MessageBrokerhost/user/password correct on both services? - Any consumer errors in Ordering logs?
MSSQL_SA_PASSWORD must meet complexity rules (this project uses SwN12345678). On Apple Silicon, check SQL Server image compatibility.
Backend services (6001/6002/6004) must be running. In Development, gateway cluster addresses in appsettings.Development.json point to localhost ports.
Kestrel is HTTP/2 only. Use a gRPC client (or BloomRPC / grpcurl) instead of a browser/curl; Basket already uses a gRPC client.
Shared libraries:
- BuildingBlocks — CQRS interfaces, MediatR behaviors, exception handler, pagination
- BuildingBlocks.Messaging —
BasketCheckoutEvent,AddMessageBroker()(MassTransit + RabbitMQ)
Basket only publishes (AddMessageBroker(config)).
Ordering publishes and consumes (AddMessageBroker(config, Assembly) → registers IConsumer<>).
This repository is intended for learning / sample microservices architecture.
See the root LICENSE file.
- Docker infrastructure is up (
catalogdb,basketdb, Redis,orderdb, RabbitMQ) - Catalog / Basket / Discount / Ordering / Gateway are running
- Basket → Discount gRPC applies discounts
- Checkout → RabbitMQ → Ordering creates an order
- Gateway routes
/catalog-service,/basket-service,/ordering-servicework