The Library Management System is a microservices-based application designed to manage various aspects of a library, including user authentication, book management, author information, and category organization.
The system is built on a microservices architecture, consisting of four main services:
- Auth Service: Handles user authentication and authorization.
- Author Service: Manages author information.
- Category Service: Deals with book categories and classifications.
- Book Service: Manages book information and operations.
Each service is containerized using Docker and orchestrated with Docker Compose. The services communicate with each other using gRPC for internal operations and expose REST APIs for external interactions. PostgreSQL is used as the database, with each service having its own database to ensure data isolation.
Method | gRPC | REST | Description |
---|---|---|---|
Register | Register |
POST /api/v1/auth/register |
User registration |
Login | Login |
POST /api/v1/auth/login |
User login |
RefreshToken | RefreshToken |
POST /api/v1/auth/refresh |
Refresh authentication token |
VerifyToken | VerifyToken |
POST /api/v1/auth/verify |
Verify authentication token |
Method | gRPC | REST | Description |
---|---|---|---|
CreateAuthor | CreateAuthor |
POST /api/v1/authors |
Create a new author |
GetAuthor | GetAuthor |
GET /api/v1/authors/{id} |
Retrieve author details |
UpdateAuthor | UpdateAuthor |
PUT /api/v1/authors/{id} |
Update author information |
DeleteAuthor | DeleteAuthor |
DELETE /api/v1/authors/{id} |
Delete an author |
ListAuthors | ListAuthors |
GET /api/v1/authors |
List all authors |
Method | gRPC | REST | Description |
---|---|---|---|
CreateCategory | CreateCategory |
POST /api/v1/categories |
Create a new category |
GetCategory | GetCategory |
GET /api/v1/categories/{id} |
Retrieve category details |
UpdateCategory | UpdateCategory |
PUT /api/v1/categories/{id} |
Update category information |
DeleteCategory | DeleteCategory |
DELETE /api/v1/categories/{id} |
Delete a category |
ListCategories | ListCategories |
GET /api/v1/categories |
List all categories |
UpdateItemCategories | UpdateItemCategories |
PUT /api/v1/items/{item_id}/categories |
Update categories for an item |
BulkAddItemToCategories | BulkAddItemToCategories |
POST /api/v1/categories/bulk-add-item |
Add an item to multiple categories |
GetItemCategories | GetItemCategories |
GET /api/v1/items/{item_id}/categories |
Get categories for an item |
Method | gRPC | REST | Description |
---|---|---|---|
CreateBook | CreateBook |
POST /api/v1/books |
Create a new book |
GetBook | GetBook |
GET /api/v1/books/{id} |
Retrieve book details |
UpdateBook | UpdateBook |
PUT /api/v1/books/{id} |
Update book information |
DeleteBook | DeleteBook |
DELETE /api/v1/books/{id} |
Delete a book |
ListBooks | ListBooks |
GET /api/v1/books |
List all books |
BorrowBook | BorrowBook |
POST /api/v1/books/{id}/borrow |
Record a book being borrowed |
ReturnBook | ReturnBook |
POST /api/v1/books/{id}/return |
Record a book being returned |
GetBookRecommendations | GetBookRecommendations |
GET /api/v1/books/{id}/recommendations |
Get book recommendations |
The Swagger JSON files are located at /api/swagger/<service_name>.swagger.json
Swagger JSON file is generated from the proto file that we define.
Swagger UI is available for each service to provide interactive API documentation. Once the services are running, you can access the Swagger UI at <sevice_host>:<service_port>
This system implements several strategies to handle race conditions and ensure data consistency across concurrent operations.
-
Connection Pooling:
- We use Go's
sql.DB
to manage a pool of database connections. - Configuration is done in the
database.NewConnection
function:db.SetMaxOpenConns(cfg.MaxOpenConns) db.SetMaxIdleConns(cfg.MaxIdleConns) db.SetConnMaxLifetime(cfg.ConnMaxLifetime)
- We use Go's
-
FOR UPDATE SKIP LOCKED:
- Used in critical sections like book borrowing to handle concurrent updates.
- Example in
BorrowBook
:query := ` UPDATE books SET available_copies = available_copies - 1 WHERE id = $1 AND available_copies > 0 RETURNING available_copies FOR UPDATE SKIP LOCKED `
-
Transactions:
- All critical operations are wrapped in database transactions to ensure atomicity.
As the system grows and becomes more complex, we may need to implement more robust solution to handle distributed transactions and ensure consistency across multiple services. Here are some approaches that could be considered:
-
Saga Pattern:
- For operations spanning multiple services, we could implement the Saga pattern.
- Each step in a distributed transaction would have a corresponding compensating action to roll back changes if a step fails.
-
Event-Driven Architecture:
- Implement an event bus or message queue (e.g., RabbitMQ, Apache Kafka) for asynchronous communication between services.
- Services would publish events when their state changes and subscribe to relevant events from other services.
The Library Management System implements several security measures to protect data and ensure secure communication between services and clients.
-
JWT for REST API:
- JSON Web Tokens (JWT) are used for authenticating REST API requests.
- The Auth Service generates JWTs upon successful login.
- Each JWT contains encoded user information and an expiration time.
- Other services validate the JWT for protected endpoints.
-
Server Key for Inter-Service Communication:
- A pre-shared server key is used to authenticate gRPC calls between services.
- Each service includes this key in its gRPC metadata for outgoing calls.
- Receiving services validate this key before processing the request.
To add a new service or extend existing ones:
- Create a new directory under
cmd/
for your service. - Define the service's proto file in
api/proto/
. - Generate the gRPC and REST gateway code using the
make proto-generate SVC=your_service_name
command. - Implement the service logic in Go by write the code in
internal/<service_name>
- Add the service to the
docker-compose.yml
file. - Update the Makefile if necessary to include build and run commands for new service.
- Docker and Docker Compose
- Go 1.21 or later
- Make
- Clone the repository:
- Set up environment variables:
Edit the
cp .env.example .env
.env
file with your configuration. - Build and start the services:
make up
- Run database migrations:
make migrate-up
- The services should now be running and accessible at their respective ports.
- Build all services:
make build
- Generate proto files:
make proto-generate SVC=service_name
- Create a new migration:
make create-table SVC=service_name SEQ=migration_name
- Stop all services:
make down
Refer to the Makefile for more available commands.