🌐 Read this in other languages: Português (Brasil)
Prototype of a .NET 8 Web API for a Library system
This project demonstrates how to build a RESTful API using .NET 8, following modern best practices for architecture, testing, and maintainability.
It simulates a simple library system with authors and books, and is designed for learning and demonstration purposes.
In addition to traditional CRUD operations, the project features an event-driven checkout workflow (EDA) using RabbitMQ and MassTransit, enabling asynchronous order processing and service decoupling.
- Features & Best Practices
- Getting Started
- Automated Tests
- Manual API Testing
- Running Code Coverage
- Project Structure
- Continuous Integration and Deployment (CI/CD)
- Observability with Jaeger, Prometheus, Loki and Grafana
- EDA: Messaging and Event-Driven Architecture
- Architecture Overview
- Example: Checkout Event Flow
- Observability: Tracing & Metrics
- License
- .NET 8 with modern C# features
- Traditional Web API with Controllers for clear separation of concerns and scalability
- Domain-driven structure (Domain, Infrastructure, DTOs, etc.)
- Dependency Injection for services and repositories, making the code more testable and maintainable
- In-memory database using Entity Framework Core for easy setup and testing
- AutoMapper for mapping between domain models and DTOs
- Custom validation attributes and use of built-in validation
- Consistent and centralized error handling with RFC-compliant
ApiProblemDetailsresponses - Unit and integration tests (xUnit, Moq, FluentAssertions), with mocks to isolate dependencies
- Automatic API documentation with Swagger/OpenAPI
- Event-Driven Architecture (EDA) using RabbitMQ and MassTransit for asynchronous communication between services
- Message-based workflow for checkout and order processing
- Separation of concerns and SOLID principles
- Environment-based configuration via
appsettings.json - Lowercase URLs and JSON serialization options for cleaner APIs
- Richardson Maturity Model: This API reaches Level 2 of the Richardson Maturity Model, meaning it uses distinct resources and proper HTTP verbs (GET, POST, PUT, DELETE) for each operation. However, it does not yet implement HATEOAS (Level 3), which would include hypermedia links in responses to guide clients dynamically. See Richardson Maturity Model for more details.
- Unified Response Pattern: All successful responses follow the
ApiResponse<T>format, ensuring consistency and predictability for API consumers. All error responses follow the RFC 7807 standard using theApiProblemDetailsobject, making error handling standardized and interoperable.
Before running the API for the first time, restore the project dependencies:
dotnet restoreFor quick local development or unit testing of authors and books:
dotnet run --project src/Library.API/Library.API.csprojThe API will be available at https://localhost:5001.
Note: Order/checkout endpoints require RabbitMQ and the full stack running.
To test the event-driven order flow, messaging, and observability:
docker compose up --buildThis will start the API, Checkout worker, RabbitMQ, and all observability tools.
The API documentation is available via Swagger UI at:
https://localhost:5001/swagger
The project includes comprehensive test coverage. Unit and integration tests are located in the tests/ directory.
- Unit Tests: Validation of individual components and methods in isolation
- Integration Tests: Verification of interactions between different application components
To execute all tests, use the command:
dotnet testtests/ directory,
which are run with dotnet test.
There are two main ways to perform manual API testing:
After starting the application, access Swagger UI at https://localhost:5001/swagger.
This interactive interface enables:
- Exploration of available endpoints
- Direct request testing in the browser
- Data model visualization
- Custom parameter request sending
The src/Library.API/Library.API.http file contains ready-to-use example requests.
These are useful for quick manual tests and workflow documentation.
Recommended tools:
- REST Client extension in VS Code (like "REST Client" by Huachao Mao)
- Postman or Insomnia
Example of manual test workflow in the .http file:
- Create an author
- List authors
- Update an author
- Create a book for the author
- List books
- Update a book
- Remove book and author
- Create a book order (checkout)
- Check the status of a book order
The commands below will generate a code coverage report in the coveragereport directory at the root of the project. You can view the coverage results by opening the index.html file in that directory with your web browser.
dotnet test --collect:"XPlat Code Coverage" --settings .coverlet.runsettings
reportgenerator -reports:"tests/**/TestResults/**/coverage.opencover.xml" -targetdir:coveragereport -reporttypes:Htmlsrc/Library.API/ # Main API project
Controllers/ # API endpoints
Domain/ # Domain models, repositories, services
DTOs/ # Data Transfer Objects
Extensions/ # Extension methods and helpers
Infrastructure/ # EF Core context, repositories, services, middlewares
Mapping/ # AutoMapper profiles
Validation/ # Custom validation attributes
src/Library.Events/ # Event contracts (messages shared between services)
src/Library.Checkout/ # Worker service for processing order events
tests/Library.API.Tests/ # Unit tests
tests/Library.API.IntegrationTests/ # Integration tests
observability/ # Observability configs (Grafana dashboards, Prometheus, Loki, provisioning)
The project implements a comprehensive and automated Continuous Integration and Continuous Deployment (CI/CD) pipeline using GitHub Actions:
-
Triggers:
- Pushes to
mainbranch - Pushes to
docs/*,feature/*,refactor/*, andtest/*branches - Pull requests to
mainbranch
- Pushes to
-
Build and Test Process:
- Sets up .NET 8 SDK
- Restores project dependencies
- Builds the project in Release configuration
- Runs comprehensive test suites:
- Unit Tests
- Integration Tests
- Generates code coverage reports
- Uploads coverage reports to Codecov for tracking
-
Trigger: Successful completion of Continuous Integration workflow on
mainbranch -
Docker Image Publishing:
- Builds a Docker image for the Library API
- Publishes image to Docker Hub
- Generates multiple tags:
- Branch-specific tags
- Semantic versioning tags
latesttag
- Automated testing for every code change
- Consistent build and deployment process
- Immediate feedback on code quality
- Automatic Docker image generation
- Code coverage tracking
To run the API together with Jaeger, Prometheus, Loki, Promtail, Grafana, and the Otel Collector, use the docker-compose.yml file:
docker compose up --build- Access the API at: http://localhost:5000 or https://localhost:5001
- Access Jaeger web interface (traces) at: http://localhost:16686
- Access Prometheus web interface (metrics) at: http://localhost:9090
- Access Grafana web interface (dashboards, logs, metrics, traces) at: http://localhost:3000 (default user/password: admin/admin)
- Access API metrics directly at: http://localhost:5000/metrics
- Access Loki API (logs) at: http://localhost:3100 (used by Grafana/Promtail)
- Access Promtail status (log collector) at: http://localhost:9080 (optional)
docker compose down- otel-collector: Receives traces, metrics, and logs from the services and forwards them to Jaeger, Prometheus, Loki, and other observability tools.
- rabbitmq: Message broker used for event-driven communication between services (management UI at http://localhost:15672, default user/password: guest/guest).
- library-api: Main .NET API, exposes REST endpoints, metrics, and logs.
- jaeger: Collector and visualizer for distributed traces (OpenTelemetry/Jaeger).
- prometheus: Metrics collector and database, scrapes the API's /metrics endpoint.
- loki: Backend for storing and indexing structured logs.
- promtail: Log collector, reads logs from containers and sends them to Loki.
- grafana: Centralized visualization for metrics, logs, and traces (dashboards, queries, alerts).
OpenTelemetry.Exporter.OpenTelemetryProtocol(for Jaeger via OTLP)OpenTelemetry.Exporter.Prometheus.AspNetCore(for Prometheus)OpenTelemetry.Extensions.HostingOpenTelemetry.Instrumentation.AspNetCoreOpenTelemetry.Instrumentation.HttpOpenTelemetry.Instrumentation.MassTransit(for distributed tracing of messaging)Serilog.AspNetCoreandSerilog.Enrichers.Span(structured logs)
The system is based on Event-Driven Architecture (EDA) using RabbitMQ and MassTransit. The main flow is:
The event-driven workflow relies on RabbitMQ as the central message broker, running as a container in the stack.
[Client] ---> [Library.API] --(OrderPlacedEvent)--> [RabbitMQ] --(OrderPlacedEvent)--> [Library.Checkout]
^ | |
| |<--(Status events: PaymentConfirmed, |
| | OrderProcessing, OrderShipped, OrderDelivered, |
| | OrderCompleted, PaymentFailed) |
| | |
|<-------------------(Status update via API)-----------------------|
- Library.API: Exposes REST endpoints, publishes events to RabbitMQ, and updates order status based on events.
- Library.Checkout: Worker service that consumes events from RabbitMQ, processes business logic (payment, shipping), and emits new events.
- Library.Events: Shared project with event/message contracts.
MassTransit(core library for distributed application messaging)MassTransit.RabbitMQ(RabbitMQ transport for MassTransit)OpenTelemetry.Instrumentation.MassTransit(distributed tracing for messaging)
Before creating a book order, you need to:
- Create an Author
- Create a Book (using the authorId from the previous response)
After that, you can:
- Create a Book Order (using the bookId from the previous response)
- The API will publish an OrderPlacedEvent to RabbitMQ
- The Checkout service will process the event and emit the following events: PaymentConfirmed, PaymentFailed, OrderProcessing, OrderShipped, OrderDelivered, and OrderCompleted.
- You can check the order status via the API
- Jaeger: http://localhost:16686 — visualize distributed traces for each order event.
- Grafana: http://localhost:3000 — dashboards for metrics and traces.
- Prometheus: http://localhost:9090 — raw metrics.
MIT