A .NET 9.0 microservices solution demonstrating inter-service communication patterns including synchronous (HTTP REST, gRPC) and asynchronous (RabbitMQ) messaging.
- Overview
- Architecture
- Services
- Technology Stack
- Communication Patterns
- Prerequisites
- Getting Started
- Configuration
- API Endpoints
- Deployment
- Project Structure
This solution consists of two microservices that work together to manage platforms and their associated commands:
- PlatformService: Manages platform entities (Name, Cost, Publisher)
- CommandsService: Manages commands associated with platforms (HowTo, CommandLine)
The services communicate using multiple patterns:
- Synchronous: HTTP REST API and gRPC
- Asynchronous: RabbitMQ Message Bus
βββββββββββββββββββ βββββββββββββββββββ
β PlatformService ββββββββββΆβ CommandsService β
β β HTTP β β
β β gRPC β β
β β β β
ββββββββββ¬βββββββββ ββββββββββ¬βββββββββ
β β
β β
βββββββββββββ¬ββββββββββββββββ
β
ββββββββΌβββββββ
β RabbitMQ β
β MessageBus β
βββββββββββββββ
-
Platform Creation: When a platform is created in PlatformService:
- Synchronous HTTP call to CommandsService (fallback)
- Asynchronous message published to RabbitMQ
- CommandsService receives message and creates platform locally
-
Platform Synchronization: CommandsService can fetch platforms via:
- gRPC call to PlatformService (preferred)
- HTTP REST API call (fallback)
Purpose: Manages platform entities
Features:
- CRUD operations for platforms
- SQL Server database (Production) / InMemory database (Development)
- Publishes platform events to RabbitMQ
- Exposes gRPC endpoint for platform data
- HTTP REST API endpoints
Ports:
- HTTP API:
5279(Development) - gRPC:
7196(Development)
Database:
- Development: InMemory Database
- Production: SQL Server
Purpose: Manages commands associated with platforms
Features:
- CRUD operations for commands
- InMemory database
- Subscribes to RabbitMQ events for platform updates
- Consumes gRPC from PlatformService
- HTTP REST API endpoints
Ports:
- HTTP API:
6000(Development)
Database:
- InMemory Database
- .NET 9.0 - Framework
- ASP.NET Core - Web framework
- Entity Framework Core 9.0.9 - ORM
- SQL Server - Production database
- RabbitMQ - Message broker
- gRPC - High-performance RPC framework
- AutoMapper - Object mapping
- Docker - Containerization
- Kubernetes - Orchestration
- OpenAPI/Swagger - API documentation
- PlatformService β CommandsService: HTTP POST when creating platforms
- Standard REST endpoints for CRUD operations
- CommandsService β PlatformService: gRPC call to fetch all platforms
- Protocol Buffers definition:
Protos/platforms.proto - More efficient than HTTP for inter-service communication
- Exchange:
trigger(Fanout type) - Event Type:
Platform_Published - PlatformService publishes platform creation events
- CommandsService subscribes and processes events asynchronously
To run the two services locally you need:
- .NET 9.0 SDK or newer
- A RabbitMQ broker on
localhost:5672, either through Docker Desktop or a local RabbitMQ install
SQL Server is not needed locally: in Development both services use an in-memory database.
To deploy to Kubernetes you additionally need:
- Docker Desktop with Kubernetes enabled (or another cluster) and
kubectl - The NGINX Ingress Controller if you want to use the ingress
The start order matters: RabbitMQ, then PlatformService, then CommandsService. CommandsService calls PlatformService over gRPC and connects to RabbitMQ while it starts, and it exits if either is missing.
git clone https://github.com/ramez00/microservices-platform-commands.git
cd microservices-platform-commandsdocker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-managementSkip this step if RabbitMQ is already installed and running on your machine. The management UI is at http://localhost:15672 (user guest, password guest).
Open a terminal in the repository root:
dotnet run --project PlatformServiceWait for these lines:
--> Using InMem Db
--> Seeding Data.....
Now listening on: http://localhost:7196
Now listening on: http://localhost:5279
| Endpoint | Address |
|---|---|
| REST API | http://localhost:5279 |
| gRPC | http://localhost:7196 |
Open a second terminal in the repository root:
dotnet run --project CommandsServiceWait for these lines:
--> Calling GRPC Server http://localhost:7196
--> Seeding new platforms...
==> Connected to RabbitMQ and listening for messages.
Now listening on: http://localhost:6000
| Endpoint | Address |
|---|---|
| REST API | http://localhost:6000 |
Follow the steps in Testing to create a platform in PlatformService and watch it arrive in CommandsService.
Press Ctrl+C in each terminal, then stop the broker with docker stop rabbitmq (start it again later with docker start rabbitmq). All data is in memory, so every restart begins with the four seeded platforms.
| Symptom | Cause and fix |
|---|---|
CommandsService exits at startup with a NullReferenceException |
PlatformService or RabbitMQ is not running. Start them first, then start CommandsService again. |
PlatformService console shows Could not connect to the Message Bus and requests return 500 |
RabbitMQ is not reachable on localhost:5672. Start it and restart PlatformService. |
http://localhost:6000 does not open in the browser (ERR_UNSAFE_PORT) |
Chrome, Edge and Firefox block port 6000. Use PowerShell, curl or Postman instead. |
address already in use |
Another process holds port 5279, 7196 or 6000. Stop it, or change the port in appsettings.json (PlatformService) or Properties/launchSettings.json (CommandsService). If you change the gRPC port, update GrpcPlatform in CommandsService too. |
| A created platform never shows up in CommandsService | CommandsService was not running when the event was published. Restart it and it reloads all platforms over gRPC. |
docker build -t <your-dockerhub-user>/platformservice PlatformService
docker build -t <your-dockerhub-user>/commandservice CommandsService
docker push <your-dockerhub-user>/platformservice
docker push <your-dockerhub-user>/commandserviceContainers run in the Production environment, where the services look for SQL Server, RabbitMQ and each other under their Kubernetes service names (see appsettings.Production.json). The images are therefore meant for the Kubernetes deployment below and do not work with a plain docker run.
appsettings.json:
{
"CommandService": "http://localhost:6000/api/c/Platforms/testPost",
"ConnectionStrings": {
"PlatformsConn": "Server=localhost,14333;Database=PlatformDb;User Id=sa;Password=P@ssw0rd!;TrustServerCertificate=True"
},
"RabbitMQHost": "localhost",
"RabbitMQPort": "5672",
"Kestrel": {
"Endpoints": {
"Grpc": {
"Protocols": "Http2",
"Url": "http://localhost:7196"
},
"webApi": {
"Protocols": "Http1",
"Url": "http://localhost:5279"
}
}
}
}appsettings.json:
{
"RabbitMQHost": "localhost",
"RabbitMQPort": "5672",
"GrpcPlatform": "http://localhost:7196"
}Test endpoint to verify service is running.
Get all platforms.
Get platform by ID.
Create a new platform (without event publishing).
Request Body:
{
"name": "Platform Name",
"cost": "Free",
"publisher": "Publisher Name"
}Create a new platform with event publishing (sends sync HTTP + async RabbitMQ).
Request Body:
{
"name": "Platform Name",
"cost": "Free",
"publisher": "Publisher Name"
}Test endpoint.
Get all platforms from CommandsService database.
Get all commands for a specific platform.
Get a specific command for a platform.
Create a new command for a platform.
Request Body:
{
"howTo": "How to do something",
"commandLine": "command --option value"
}The manifests are in the K8S directory. Apply them in this order from the repository root.
-
Create the SQL Server password secret
The SQL Server deployment reads its
sapassword from a secret namedmssql. It must match the password in thePlatformsConnconnection string inPlatformService/appsettings.Production.json.kubectl create secret generic mssql --from-literal=SA_PASSWORD="P@ssw0rd!" -
Deploy SQL Server
kubectl apply -f K8S/local-pvc.yaml kubectl apply -f K8S/mssql-plat-depl.yaml
-
Deploy RabbitMQ
kubectl apply -f K8S/rabbitmq-depl.yaml
-
Deploy PlatformService
kubectl apply -f K8S/platforms-deply.yaml
-
Deploy CommandsService
kubectl apply -f K8S/commands-deply.yaml
-
Deploy the Ingress (optional, needs the NGINX Ingress Controller)
kubectl apply -f K8S/ingress-srv.yaml
The ingress routes the host
acme.com. Add this line to your hosts file (C:\Windows\System32\drivers\etc\hostson Windows,/etc/hostson Linux and macOS):127.0.0.1 acme.comThe APIs are then available at
http://acme.com/api/platformandhttp://acme.com/api/c/platforms. -
Check the result
kubectl get pods kubectl get services kubectl logs deployment/platforms-depl kubectl logs deployment/commands-depl
All pods should reach
Running. If CommandsService started before PlatformService or RabbitMQ were ready, restart it:kubectl rollout restart deployment commands-depl
The deployments pull pre-built images from Docker Hub:
ramez007/platformservice:latestramez007/commandservice:latest
To deploy your own code changes, build and push the images under your own Docker Hub user (see Build the Docker images), change the image: lines in K8S/platforms-deply.yaml and K8S/commands-deply.yaml, and apply the manifests again.
Soluation/
βββ PlatformService/
β βββ Controllers/
β β βββ PlatformController.cs
β βββ Data/
β β βββ ApplicationDbContext.cs
β β βββ IPlatformRepo.cs
β β βββ PlatformRepo.cs
β β βββ PrepDb.cs
β βββ Models/
β β βββ Platform.cs
β βββ Dtos/
β β βββ PlatformDto.cs
β β βββ PlatformPublishedDto.cs
β βββ AsyncDataServices/
β β βββ IMessageBusClient.cs
β β βββ MessageBusClient.cs
β βββ SyncDataServices/
β β βββ Http/
β β β βββ HttpCommandDataClient.cs
β β β βββ ICommandDataClient.cs
β β βββ Grpc/
β β βββ GrpcPlatformService.cs
β βββ Protos/
β β βββ platforms.proto
β βββ Mapping/
β β βββ ProfileMapping.cs
β βββ Migrations/
β βββ Program.cs
β βββ Dockerfile
β βββ PlatformService.csproj
β
βββ CommandsService/
β βββ Controllers/
β β βββ CommandsController.cs
β β βββ PlatformsController.cs
β βββ Data/
β β βββ AppDbContext.cs
β β βββ ICommandRepo.cs
β β βββ CommandRepo.cs
β β βββ PrepDb.cs
β βββ Models/
β β βββ Command.cs
β β βββ Platform.cs
β βββ Dtos/
β β βββ CommandDtos.cs
β β βββ PlatformDto.cs
β β βββ PlatformPublishedDto.cs
β β βββ GenericEventDto.cs
β βββ AsyncDataServices/
β β βββ MessageBusSubscriber.cs
β βββ SyncDataServices/
β β βββ Grpc/
β β βββ IPlatformDataClient.cs
β β βββ PlatformDataClient.cs
β βββ EventProcessing/
β β βββ IEventProcessor.cs
β β βββ EventProcessor.cs
β βββ MapperProfiles/
β β βββ CommandsProfile.cs
β βββ Protos/
β β βββ platforms.proto
β βββ Program.cs
β βββ Dockerfile
β βββ CommandsService.csproj
β
βββ K8S/
βββ platforms-deply.yaml
βββ commands-deply.yaml
βββ rabbitmq-depl.yaml
βββ mssql-plat-depl.yaml
βββ ingress-srv.yaml
βββ local-pvc.yaml
The EventProcessor class handles incoming RabbitMQ messages:
- Detects event type from message
- Processes
Platform_Publishedevents - Creates platform entries in CommandsService database
- Prevents duplicate entries using ExternalID
The MessageBusClient class:
- Connects to RabbitMQ
- Publishes platform creation events
- Uses Fanout exchange for pub/sub pattern
The GrpcPlatformService class:
- Implements gRPC service interface
- Exposes
GetAllPlatformsmethod - Returns platform data efficiently
The PlatformDataClient class:
- Connects to PlatformService gRPC endpoint
- Fetches all platforms
- Maps gRPC models to domain models
With both services running locally, these steps exercise all three communication paths. The commands are for PowerShell; the curl equivalents follow.
-
List the platforms in PlatformService (four are seeded)
Invoke-RestMethod http://localhost:5279/api/Platform
-
List the platforms in CommandsService β the same four, copied over gRPC at startup
Invoke-RestMethod http://localhost:6000/api/c/Platforms/platforms
-
Create a platform β this publishes a
Platform_Publishedevent to RabbitMQInvoke-RestMethod -Method Post http://localhost:5279/api/Platform/CreatePlatForm ` -ContentType 'application/json' ` -Body '{"name":"Kubernetes","cost":"Free","publisher":"CNCF"}'
The CommandsService console shows:
--> Message received from RabbitMQ. --> Platform Published Event Detected --> Platform added! -
List the platforms in CommandsService again β the new platform is now there
Invoke-RestMethod http://localhost:6000/api/c/Platforms/platforms
-
Add a command to a platform and read it back
Invoke-RestMethod -Method Post http://localhost:6000/api/c/platforms/1/Commands ` -ContentType 'application/json' ` -Body '{"howTo":"Build a project","commandLine":"dotnet build"}' Invoke-RestMethod http://localhost:6000/api/c/platforms/1/Commands
In Windows PowerShell use curl.exe, because curl is an alias for Invoke-WebRequest there.
curl http://localhost:5279/api/Platform
curl http://localhost:6000/api/c/Platforms/platforms
curl -X POST http://localhost:5279/api/Platform/CreatePlatForm \
-H "Content-Type: application/json" \
-d '{"name":"Kubernetes","cost":"Free","publisher":"CNCF"}'
curl -X POST http://localhost:6000/api/c/platforms/1/Commands \
-H "Content-Type: application/json" \
-d '{"howTo":"Build a project","commandLine":"dotnet build"}'
curl http://localhost:6000/api/c/platforms/1/Commands- Development Mode: Both services use InMemory databases for quick development
- Production Mode: PlatformService switches to SQL Server
- Event-Driven: CommandsService automatically receives platform updates via RabbitMQ
- Fallback Mechanism: If RabbitMQ fails, HTTP sync call is attempted
- gRPC: More efficient than HTTP for inter-service communication
- Update default passwords in production
- Use Kubernetes secrets for sensitive configuration
- Implement authentication/authorization for production
- Use HTTPS/TLS for production deployments
- Secure RabbitMQ with authentication