DEV Community

amrali21
amrali21

Posted on

I built a simplified microservice reference project with guide on how to run locally, using docker-compose or Kubernetes + deployment to AKS

Today I'm showcasing a project I've been working on for a while: Ledgerly, a simplified microservices reference project built with .NET and Angular.

I built Ledgerly to be be focused purely on microservices best practices.

What it demonstrates:
πŸ”Ή Database-per-service β€” 4 services, 4 independent databases

πŸ”Ή gRPC for synchronous service-to-service calls

πŸ”Ή RabbitMQ for async events β€” invoice status changes publish events that a dashboard service consumes to keep KPIs in sync, with zero direct coupling

πŸ”Ή API gateway + service discovery (Eureka locally/Docker, Kubernetes DNS in K8s)

πŸ”Ή JWT auth issued by its own service

It also ships as a full deployment guide β€” run it locally, in Docker Compose, or on Kubernetes, including a walkthrough for deploying the cluster to Azure (AKS).
Built as a boilerplate: fork it, swap in your own domain, keep the architecture.

Services:
description of gif

Databases

Each of the four .NET services has its own SQL Server database β€” there is
no shared database. All four databases live on the host machine, even
when the services themselves run in Docker or Kubernetes β€” this keeps setup
simple (no containerized SQL Server to manage):

Service Database
Invoice service ledgerly-invoice
Customer service ledgerly-customer
Dashboard service ledgerly-dashbaord
Auth service ledgerly-auth

How service discovery works

  • Local / Docker: services register themselves with Eureka, and the Ocelot gateway resolves downstream services by name through Eureka.
  • Kubernetes: Eureka is removed. Each service is exposed by a Kubernetes Service (ClusterIP), and the gateway routes directly to those service names. ClusterIP load-balances across pods.

Prerequisites

Install these on your host machine before running the project, in any mode:

  1. JDK (Java 11+) β€” to build/run the Eureka server (eureka-server/). Needed for local and Docker modes; Kubernetes doesn't use Eureka.
  2. SQL Server Express β€” all four databases run on the host in every mode (local, Docker, and Kubernetes). See Databases above.
  3. .NET 10 SDK (Developer Pack) β€” to build/run the services locally. Note: ledgerly-backend-invoice-service targets net6.0, so the matching .NET 6 runtime is required alongside .NET 10.
  4. RabbitMQ, via Chocolatey β€” needed for local mode only (Docker Compose and Kubernetes each run their own RabbitMQ container):
   choco install rabbitmq -y
Enter fullscreen mode Exit fullscreen mode

This pulls in Erlang as a dependency β€” restart the RabbitMQ service
afterwards so it picks up the Erlang install:

   Restart-Service RabbitMQ
Enter fullscreen mode Exit fullscreen mode
  1. Create the databases β€” run every script in db/ against your SQL Server instance (creates the four databases, schema, and seed data) before starting any service.

Running the project

You can run the stack in three modes.

1. Local (dotnet + Eureka)

Start the Eureka discovery server first, then launch all the .NET services and
the frontend with the batch script.

# 1. Start Eureka (in its own terminal; leave it running)
cd eureka-server
.\gradlew bootRun

# 2. From the repo root, build + start all services and the Angular frontend
run-all.bat
#   run-all.bat nobuild   # skip the build, just start everything
Enter fullscreen mode Exit fullscreen mode

run-all.bat opens a separate window per service:

  • invoice β†’ https://localhost:7052
  • customer β†’ https://localhost:7099
  • dashboardβ†’ https://localhost:7063
  • auth β†’ https://localhost:7109
  • gateway β†’ https://localhost:7019
  • frontend β†’ http://localhost:4200

Connection string: each service reads ConnectionStrings:DefaultConnection
from its own appsettings.json (e.g.
ledgerly-backend-invoice-service/appsettings.json). Update the Data Source,
User ID, and Password there to match your local SQL Server Express
instance.

2. Docker Compose

Builds and runs every service β€” including Eureka β€” as containers on a shared
network. The database stays on the host machine (host.docker.internal\SQLEXPRESS).

# DB credentials come from a .env file (SQL_USER / SQL_PASSWORD)
docker-compose up --build
Enter fullscreen mode Exit fullscreen mode

Exposed ports: frontend 4200, gateway 8080, invoice 5052, customer 5246,
dashboard 5208, auth 5109, Eureka 8761. Services wait for Eureka to be
healthy before starting.

Connection string: copy .env.example to .env and set SQL_USER /
SQL_PASSWORD, plus one connection string per service β€” INVOICE_DB,
CUSTOMER_DB, DASHBOARD_DB, AUTH_DB. These are injected into each
container as ConnectionStrings__DefaultConnection.

3. Kubernetes (Local + Deploy to AKS)

On Kubernetes Eureka is not used. Instead, each deployment is exposed by a
Kubernetes Service, and the API gateway routes traffic directly to those
services (Kubernetes DNS + ClusterIP handle discovery and load-balancing).
Every service has its own Deployment, so each can be scaled independently
for maximum scalability (with HPAs autoscaling on CPU).

A single Ingress is what allows connections into the Kubernetes cluster from
outside β€” it is the only public entry point. It routes by path, and the most
important entry point is /, which points to the Angular frontend (the
/InvoiceGW, /CustomerGW, /Dashboard, /AuthGW paths go to the gateway).

Connection string: copy k8s/01-db-secret.example.yaml to
k8s/01-db-secret.yaml and fill in the ConnectionStrings__DefaultConnection
value in each of the four secrets. For local minikube testing, point at
host.minikube.internal,1433 (not the \SQLEXPRESS named instance, and not
an AKS/cloud DB) β€” see the comments in the example file for the one-time SQL
Server Express config this requires.

kubectl apply -k k8s/

# route local traffic to the ingress controller (instead of using `minikube tunnel`)
kubectl port-forward -n ingress-nginx service/ingress-nginx-controller 8090:80
Enter fullscreen mode Exit fullscreen mode

Then open the app at http://myapp.local:8090 (after mapping myapp.local
to the minikube IP in your hosts file β€” see the k8s README).

See k8s/README.md for full details β€” image builds, the DB

The projectπŸ”—: [https://lnkd.in/d_NeksRk]

Top comments (0)