The Library service is a Go-based application for managing books and authors through a REST-to-gRPC API, now integrated with a PostgreSQL database and an outbox pattern for asynchronous event handling. Initially developed with an in-memory storage solution, this project has evolved to incorporate persistent storage and transactional consistency, with plans for further enhancements.
The implementation adheres to the go-clean-template structure and follows specific technological and testing requirements outlined in the assignments.
-
Book Management:
- Add a new book (
POST /v1/library/book). - Update an existing book (
PUT /v1/library/book). - Retrieve book information by ID (
GET /v1/library/book/{id}).
- Add a new book (
-
Author Management:
- Register a new author (
POST /v1/library/author). - Update author information (
PUT /v1/library/author). - Retrieve author information by ID (
GET /v1/library/author/{id}). - Retrieve all books by a specific author (
GET /v1/library/author_books/{author_id}).
- Register a new author (
-
Validation:
- Book and author IDs are UUIDs, generated automatically by the database (
uuid_generate_v4()). - Author names must match the regex
^[A-Za-z0-9]+( [A-Za-z0-9]+)*$and have a length between 1 and 512 characters. - Additional validation aligns with tests and common sense.
- Book and author IDs are UUIDs, generated automatically by the database (
-
Database Integration:
- PostgreSQL database with tables for
author,book, andauthor_book(many-to-many relationship). - Automatic timestamp updates via triggers (
created_at,updated_at). - Indexes on
author.nameandbook.namefor efficient queries. - Cascade deletion in
author_bookto maintain consistency.
- PostgreSQL database with tables for
-
Outbox Pattern:
- Asynchronous event handling for book and author creation.
outboxtable stores events with statuses (CREATED,IN_PROGRESS,SUCCESS).- Sends
POSTrequests to configured URLs (OUTBOX_AUTHOR_SEND_URL,OUTBOX_BOOK_SEND_URL) withAuthorIDorBookID.
-
Transactional Consistency:
- Uses
pgxfor database operations with transaction support. - Implements a
Transactorinterface for atomic operations.
- Uses
-
Logging: Structured logging with zap.
-
Signal Handling: Graceful shutdown on
SIGINTandSIGTERM.
The project follows the go-clean-template structure. Key components include:
- Protobuf Definitions: API defined in library.proto.
- Database Migrations: Managed in db/migrations using goose.
- Generated Code: Handled via Makefile and easyp.yaml.
- Integration Tests: Located in integration_test.
- Configuration: Environment variables for gRPC, PostgreSQL, and outbox settings.
The API, defined in library.proto, includes created_at and updated_at fields for books. Endpoints:
| Method | Endpoint | Description |
|---|---|---|
| POST | /v1/library/book |
Add a new book |
| PUT | /v1/library/book |
Update an existing book |
| GET | /v1/library/book/{id} |
Get book details by ID |
| POST | /v1/library/author |
Register a new author |
| PUT | /v1/library/author |
Update author information |
| GET | /v1/library/author/{id} |
Get author details by ID |
| GET | /v1/library/author_books/{author_id} |
Get all books by an author |
View the API in Swagger Editor using swagger.json.
The PostgreSQL database includes the following tables:
-
author:
id: UUID (primary key, auto-generated).name: Text (validated, indexed).created_at,updated_at: Timestamps (updated via trigger).
-
book:
id: UUID (primary key, auto-generated).name: Text (indexed).created_at,updated_at: Timestamps (updated via trigger).
-
author_book:
author_id,book_id: UUIDs (composite primary key, foreign keys withON DELETE CASCADE).- Index on
book_idfor efficient queries.
-
outbox:
idempotency_key: Text (primary key).data: JSONB (event payload).status: Enum (CREATED,IN_PROGRESS,SUCCESS).kind: Integer (event type).created_at,updated_at: Timestamps.
Migrations are stored in db/migrations and applied via migrate.go.
- Language: Go
- Database: PostgreSQL with pgx driver.
- Migrations: goose.
- gRPC: Core API implementation.
- gRPC Gateway: REST-to-gRPC translation (grpc-gateway).
- Logging: zap.
- Validation: protoc-gen-validate.
The service uses the following environment variables:
| Variable | Description |
|---|---|
GRPC_PORT |
gRPC server port |
GRPC_GATEWAY_PORT |
REST-to-gRPC gateway port |
POSTGRES_HOST |
PostgreSQL host |
POSTGRES_PORT |
PostgreSQL port |
POSTGRES_DB |
Database name |
POSTGRES_USER |
Database user |
POSTGRES_PASSWORD |
Database password |
POSTGRES_MAX_CONN |
Maximum database connections |
OUTBOX_ENABLED |
Enable/disable outbox |
OUTBOX_WORKERS |
Number of outbox workers |
OUTBOX_BATCH_SIZE |
Batch size for outbox processing |
OUTBOX_WAIT_TIME_MS |
Wait time between outbox batches (ms) |
OUTBOX_IN_PROGRESS_TTL_MS |
TTL for in-progress outbox messages (ms) |
OUTBOX_AUTHOR_SEND_URL |
URL for author event POST requests |
OUTBOX_BOOK_SEND_URL |
URL for book event POST requests |
Example PostgreSQL URL:
postgres://user:password@host:port/dbname?sslmode=disable&pool_max_conns=10
- Go (latest stable version).
- protoc for gRPC code generation.
- Docker for PostgreSQL.
- WSL (recommended for Windows).
- make.
-
Clone the repository:
git clone <repository-url> cd library
-
Install dependencies:
go mod tidy
-
Generate code and mocks:
make generate
-
Start the database:
docker-compose up -d
-
Apply migrations:
go run db/migrations/migrate.go
-
Manage containers:
docker ps -a # List containers docker stop <container-id> # Stop container docker rm <container-id> # Remove container docker volume ls # List volumes docker volume rm <volume-name> # Remove volume
-
Set environment variables:
export GRPC_PORT=50051 export GRPC_GATEWAY_PORT=8080 export POSTGRES_HOST=localhost export POSTGRES_PORT=5432 export POSTGRES_DB=library export POSTGRES_USER=postgres export POSTGRES_PASSWORD=postgres export POSTGRES_MAX_CONN=10 export OUTBOX_ENABLED=true export OUTBOX_WORKERS=2 export OUTBOX_BATCH_SIZE=10 export OUTBOX_WAIT_TIME_MS=1000 export OUTBOX_IN_PROGRESS_TTL_MS=60000 export OUTBOX_AUTHOR_SEND_URL=http://example.com/author export OUTBOX_BOOK_SEND_URL=http://example.com/book
-
Build and run:
make build go run cmd/library/main.go
Run all tests:
make testRun linter:
make lintRun full cycle (lint + tests):
make allUpdate integration tests:
make update