Skip to content

Repository files navigation

Smart Library

A library distributed system built with microservices for book management and lending.

Table of Contents

Overview

The Smart Library project has two main services, one for book management, allowing you to include new books and search. The other allows users to create a lend request for a book. The microservices for book inventory and lending work individually, each with its own database, and they communicate internally by using events and gRPC.

Motivation

This project has been developed as a learning practice after studying the microservices course by Les Jackson available on YouTube. It has been a great addition to the studies since I had to figure out a few things and edge cases.

Challenges

  • Migrating from Automapper to Mapperly: The newer versions of Autommaper includes an awful warning in the console regarding their licensing, I disliked that behavior so I had to figure out how to implement Mapperly instead.

  • RabbitMQ is now Async by default: I had to learn how to work with BackgroundServices in .NET to properly add the message bus on the project. I also wanted it to not hang the thread when the service was starting up, so it was a great learning experience.

  • Nginx Ingress is deprecating soon: The new recommended thing is the Gateway API, I struggled a bit with the way they explained in the docs, saying it has now 3 personas, and stuff like that. In reality is just a bit more modular, I'm glad I managed to get that up and running.

Usage

The SmartLibrary API provides endpoints to manage books (inventory) and book lending operations.

Base URL (Docker Compose):http://localhost/api

Base URL (Kubernetes):http://localhost:8123/api

API Overview

Inventory Service

MethodEndpointDescription
GET/inventoryGet all books
GET/inventory/{id}Get book by {id}
POST/inventoryAdd new book

Lending Service

MethodEndpointDescription
POST/lendingCreate a new lend
GET/lending/{id}Get lend by {id}

Inventory Service

Get All Books

curl -X GET http://localhost/api/inventory

Get Individual Book

curl -X GET http://localhost/api/inventory/{id}

Example:

curl -X GET http://localhost/api/inventory/1

Add New Book

curl -X POST http://localhost/api/inventory \
-H "Content-Type: application/json" \
-d '{ "title": "Array That Never Ends", "cost": 32.99, "stock": 2 }'

Lending Service

Create Lend

curl -X POST http://localhost/api/lending \
-H "Content-Type: application/json" \
-d '{ "bookid": 1, "borrower": "User" }'

Get Individual Lend

curl -X GET http://localhost/api/lending/{id}

Example:

curl -X GET http://localhost/api/lending/1

System Architecture

graph TD
Client(["👤 Client\nInsomnia / curl"])
subgraph gw["API Gateway"]
direction LR
TK["🔀 Traefik\nDocker Compose"]
EV["🔀 Envoy Gateway\nKubernetes"]
end
subgraph inv["📦 Inventory Service"]
direction TB
I_REST["REST API\n/api/inventory"]
I_GRPC["gRPC Server\ninventory.proto"]
I_BUS["RabbitMQ Publisher\nBackgroundService"]
I_DB[("MSSQL\nproduction")]
I_MEM[("In-Memory DB\ndev / test")]
I_REST --- I_DB
I_REST -. dev .-> I_MEM
end
subgraph lend["📋 Lending Service"]
direction TB
L_REST["REST API\n/api/lending"]
L_GRPC["gRPC Client\ninventory.proto"]
L_SUB["RabbitMQ Subscriber\nBackgroundService"]
L_DB[("In-Memory DB")]
L_REST --- L_DB
end
MQ[["🐇 RabbitMQ\nMessage Bus"]]
Client -->|"HTTP /api/*"| gw
gw -->|"HTTP :8080"| I_REST
gw -->|"HTTP :8080"| L_REST
gw -->|"gRPC h2c :9090"| I_GRPC
L_GRPC -->|"sync · check stock"| I_GRPC
I_BUS -->|"publish events"| MQ
MQ -->|"subscribe"| L_SUB
Loading

Tech Stack

CategoryTechnologyPurpose
LanguageC# / ASP.NET Core (.NET 10)Core framework for both microservices
MappingRiok.MapperlySource-generated object mapping
APIgRPC (Grpc.AspNetCore)Sync inter-service communication
MessagingRabbitMQ 4 (RabbitMQ.Client 7.x)Async event bus via Background Services
DatabaseMicrosoft SQL ServerPersistent storage (Inventory Service)
DatabaseEF Core 10 + InMemoryORM, migrations, and lightweight dev/test store
GatewayTraefikReverse proxy for Docker Compose
GatewayEnvoy GatewayIngress via Kubernetes Gateway API
ContainersDocker / Docker ComposeLocal containerized dev
OrchestrationKubernetes + MinikubeLocal cluster setup

Development

Docker Compose

Prerequisites:

  • docker
  • docker-compose

Inside the root project folder. Run:

docker compose up -d

Kubernetes

Prerequisites:

  • docker
  • kubectl
  • minikube

Initial setup

After you've started the Kubernetes cluster in Minikube, proceed with the following steps:

Build the microservices docker image. They must be built inside the minikube environment

# To point your shell to minikube's docker-daemon, run:eval$(minikube -p minikube docker-env)
docker build -t mateusabelli/inventoryservice InventoryService
docker build -t mateusabelli/lendingservice LendingService

TODO: Rename the manifests to use development tag instead of mateusabelli for the images.

Create the database password secret: DBPassword12!

kubectl create secret generic mssql --from-literal=MSSQL_SA_PASSWORD='DBPassword12!'

Installing Envoy Docs

# Install the Gateway API CRDs and Envoy Gateway:
helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.7.1 -n envoy-gateway-system --create-namespace
# Wait for Envoy Gateway to become available:
kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available
# Install the GatewayClass, Gateway, HTTPRoute and example app:
kubectl apply -f https://github.com/envoyproxy/gateway/releases/download/v1.7.1/quickstart.yaml -n default

Installing RabbitMQ Operator Docs

# Install the RabbitMQ Cluster Operator
kubectl apply -f "https://github.com/rabbitmq/cluster-operator/releases/latest/download/cluster-operator.yml"# Apply the rabbit-mq-depl inside the "K8S" folder
kubectl apply -f K8S/rabbitmq-depl.yaml
# Get the required credentials for Production build
username="$(kubectl get secret rabbitmq-depl-default-user -o jsonpath='{.data.username}'| base64 --decode)"echo"username: $username"
password="$(kubectl get secret rabbitmq-depl-default-user -o jsonpath='{.data.password}'| base64 --decode)"echo"password: $password"# Simulated output (it will be random)# username: default_user_lMCS4yswJGo-GKiOCh9# password: 9gqfjaQ7YzBCSShNq8jn32d8cOHxOEUn

With that generated username and password, update the appsettings.Production.json in InventoryService and LendingService

Applying manifests

Run the following command to apply all the manifests in the "K8S" folder

kubectl apply -f K8S

This will do the following operations (not in the same order):

  • Create the Gateway with Envoy mapped to the Pods Cluster Ips
  • Create the Pod with Cluster IP for the InventoryService and LendingService
  • Create MSSQL Database with its own LoadBalancer for external access
  • Create the RabbitMQ Deployment

Note

Run kubectl get all to check if any service is 0/1, if so, it might be needed to restart them.

TODO: Test this development guide further to make the kubernetes setup easier.

Running locally

Using an API client such as Insomnia or Postman. Try to run the following operations to check if everything is running okay.

Please refer to the Usage guide

  • Get all books from InventoryService
  • Get a book by id from InventoryService
  • Create a lend request from LendingService
  • Get all books again, to check if stock has been subtracted.

If everything is running properly the services should be communicating via the message bus and grpc. If you keep creating lending requests and the book runs out of stock, it should return a 200 status code with the message "out of stock".

How to Contribute

All contributions are welcome and much appreciated!

There are other ways of contributing. Read more

License

This project is under the MIT license, read the LICENSE file for more details.

About

A distributed system built with microservices for book management and lending.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages