GoCommerce - Containerized E-Commerce Backend
This repository contains a containerized e-commerce platform built as ASP.NET Core 8 microservices behind a single YARP API Gateway. The system uses Entity Framework Core, SQL Server, RabbitMQ, Docker Compose, and Podman Compose.
Project Architecture
The platform follows a database-per-service architecture. Client traffic enters through the API Gateway on port 5000, while the Blazor frontend is available on port 5005. Backend services and databases stay on the internal container network and are not exposed directly to the host.
- API Gateway (
5000): YARP reverse proxy that routes client requests to the correct service and exposes an aggregated endpoint atGET /api/aggregate/orders/{orderId}. - Blazor Frontend (
5005): UI client that talks only to the API Gateway. - Product Service (internal): Owns product catalog data and consumes
OrderCreatedevents to reduce stock. - Customer Service (internal): Owns customer records.
- Order Service (internal): Creates and cancels orders, validates product/customer existence, and publishes
OrderCreatedandOrderCancelledevents. - Shipping Service (internal): Owns shipment data, validates orders over HTTP, and consumes
OrderCancelledevents to cancel shipments. - RabbitMQ (
15672management UI): Message broker for asynchronous event delivery.
Key Technical Decisions
- API Gateway: YARP provides centralized routing for
/api/products,/api/customers,/api/orders, and/api/shipments. It also exposes an aggregation endpoint that merges order, customer, and product data for a single client call. - Internal service isolation: In line with the course API Gateway guidance, backend services are reachable only through container networking. Clients and browsers do not call service containers directly.
- DTO design: All services use request and response DTOs so EF Core entities remain internal implementation details.
- Synchronous communication: Order creation validates customers and products over HTTP. Shipment creation validates the referenced order over HTTP.
- Asynchronous communication: Order Service publishes
OrderCreatedandOrderCancelledevents through RabbitMQ fanout exchanges. Product Service and Shipping Service consume those events and react independently. - Fail-fast event publishing: Order creation and cancellation no longer return success if RabbitMQ publication fails. The request is rolled back and the API returns
503 Service Unavailable, preventing silent inconsistency. - Automatic migrations: Each service applies EF Core migrations on startup with retry logic for database readiness.
Running the System
Prerequisites
- Fedora/RHEL:
podmanandpodman-compose - Other platforms: Docker Desktop and
docker compose
Podman (Fedora) first-time setup
If Podman Desktop reports Socket not found: /run/user/1000/podman/podman.sock, enable the rootless Podman API socket:
systemctl --user enable --now podman.socket
systemctl --user status podman.socketPre-pull shared base images before the first podman-compose up --build. The six .NET services all share aspnet:8.0 / sdk:8.0, and podman-compose does not deduplicate pulls across services — without a pre-pull, each service independently re-downloads the same layers from the slow MCR CDN and stalls the build. Pulling once populates the local cache:
podman pull mcr.microsoft.com/dotnet/aspnet:8.0
podman pull mcr.microsoft.com/dotnet/sdk:8.0
podman pull mcr.microsoft.com/mssql/server:2022-latest
podman pull docker.io/library/rabbitmq:3-managementIntermittent Temporary failure in name resolution during pulls is DNS flapping on the host; simply re-run the pull and Podman will resume skipped layers.
Podman (Fedora) commands
podman-compose up --buildDocker commands
docker compose up --buildThis starts 11 containers:
- 4 SQL Server database containers (internal only)
- 4 ASP.NET Core backend API containers (internal only)
- 1 RabbitMQ container with management UI at http://localhost:15672
- 1 API Gateway at http://localhost:5000
- 1 Blazor frontend at http://localhost:5005
Stopping the System
podman-compose downdocker compose downTo remove volumes for a clean reset:
podman-compose down -vdocker compose down -vReclaiming disk from unused Podman artifacts
After iterating on builds, dangling images and build layers add up quickly. To reclaim space without touching running containers:
podman system df # show what's using disk
podman image prune -f # remove dangling (untagged) images
podman builder prune -f # remove old build cache layers
podman container prune -f # remove stopped containersFor a full wipe of everything not currently in use (containers, networks, images, build cache), use:
podman system prune -a -f --volumesWarning: --volumes deletes unused volumes, which wipes SQL Server data from stopped stacks. Skip that flag if you want to preserve database state across podman-compose down and up cycles.
What Is Exposed to the Host
http://localhost:5000-> API Gatewayhttp://localhost:5005-> Blazor frontendhttp://localhost:15672-> RabbitMQ management UI (guest/guest)
The Product, Customer, Order, Shipping, and SQL Server containers are intentionally internal-only.
Testing Through the Gateway
All API requests should go through http://localhost:5000.
- Create a product
curl -X POST http://localhost:5000/api/products \
-H "Content-Type: application/json" \
-d '{"name":"Widget","description":"A test widget","price":9.99,"stockQuantity":100}'- Create a customer
curl -X POST http://localhost:5000/api/customers \
-H "Content-Type: application/json" \
-d '{"firstName":"John","lastName":"Doe","email":"john@example.com","address":"123 Main St"}'- Create an order
curl -X POST http://localhost:5000/api/orders \
-H "Content-Type: application/json" \
-d '{"customerId":1,"items":[{"productId":1,"quantity":2}]}'If publishing succeeds, Product Service consumes the OrderCreated event and decrements stock.
- Verify stock through the gateway
curl http://localhost:5000/api/products/1- Create a shipment
curl -X POST http://localhost:5000/api/shipments \
-H "Content-Type: application/json" \
-d '{"orderId":1,"shippingAddress":"123 Main St"}'- Use the aggregated endpoint
curl http://localhost:5000/api/aggregate/orders/1- Cancel the order
curl -X DELETE http://localhost:5000/api/orders/1If publishing succeeds, Shipping Service consumes the OrderCancelled event and cancels the shipment.
- Verify the shipment through the gateway
curl http://localhost:5000/api/shipments/1- Test DTO validation
curl -X POST http://localhost:5000/api/customers \
-H "Content-Type: application/json" \
-d '{"firstName":"John"}'This returns a 400 Bad Request with validation errors.
Operational Notes
- API Gateway Swagger is available at
http://localhost:5000(served at the root) for the gateway-hosted aggregation endpoint. - Reverse-proxied YARP routes are configuration-driven, so they do not automatically appear in Swagger even though they are reachable through the gateway.
- RabbitMQ management is available at
http://localhost:15672and is useful for checking exchanges, queues, and message flow during demos. - A
503 Service Unavailablefrom order creation or cancellation indicates the system intentionally rejected the request because the RabbitMQ publish step did not complete.