Simple ASP.NET Core Web API for managing products using a Clean Architecture approach.
- .NET 8
- ASP.NET Core Web API
- Dapper
- PostgreSQL
- xUnit
- Docker
ProductService.sln
src/
ProductService.API/
Controllers/
HealthChecks/
Program.cs
ProductService.API.csproj
ProductService.Application/
Interfaces/
Services/
ProductService.Application.csproj
ProductService.Domain/
Entities/
ProductService.Domain.csproj
ProductService.Infrastructure/
Repositories/
ProductService.Infrastructure.csproj
tests/
ProductService.Application.Tests/
Dockerfile
docker-compose.yml
This project is split into 4 layers:
ProductService.DomainContains the core application entity, such asProduct.ProductService.ApplicationContains business logic and abstractions, such asIProductRepositoryandProductService.ProductService.InfrastructureContains the PostgreSQL data access implementation using Dapper.ProductService.APIContains controllers, health checks, dependency injection, and application startup.
Request flow:
- The request enters a controller in the API layer.
- The controller calls a service in the Application layer.
- The service uses a repository interface instead of a direct implementation.
- The actual repository implementation lives in the Infrastructure layer.
- Core entities remain in the Domain layer.
- CRUD product
- Health check endpoint
- Docker support
- Structured logging with Serilog
- Elasticsearch log shipping
- Elastic APM tracing
- Unit tests for the application layer
GET /health
GET /api/productsGET /api/products/{id}POST /api/productsPUT /api/products/{id}DELETE /api/products/{id}
For POST /api/products or PUT /api/products/{id}:
{
"name": "ASUS Laptop",
"description": "Laptop for development work",
"price": 15000000,
"stock": 10
}Make sure .NET 8 SDK is installed.
dotnet restore ProductService.sln
dotnet build ProductService.sln
dotnet run --project src/ProductService.API/ProductService.API.csprojThe default development URL usually follows launchSettings.json. If you want to set it explicitly:
dotnet run --project src/ProductService.API/ProductService.API.csproj --urls http://localhost:8080The connection string is read from the ConnectionStrings:Postgres key.
Example format:
Host=localhost;Port=5432;Database=productdb;Username=postgres;Password=postgres
Configuration files:
src/ProductService.API/appsettings.jsonsrc/ProductService.API/appsettings.Development.json
Build the API image:
docker build -t product-service .Run with compose:
docker compose up --buildNotes:
docker-compose.ymlonly runs the API service.- The API still uses a PostgreSQL instance that is assumed to already exist on the external Docker network
local_network. - The container connection string is provided through the
ConnectionStrings__Postgresenvironment variable indocker-compose.yml. - The Elasticsearch sink target is provided through
Serilog__WriteTo__1__Args__nodes__0. - The Elastic APM Server endpoint is provided through
ElasticApm__ServerUrl.
The API uses Serilog for structured logging.
- Request logs are written through
UseSerilogRequestLogging(). - Application and repository logs are written through injected
ILogger<T>. - Logs are sent to both the console and Elasticsearch.
- API request duration is emitted in the request log event.
- Database execution duration is emitted in repository log events.
Default Elasticsearch endpoint:
http://localhost:9200
Important log fields:
TransactionIdOperationLayerTraceIdSpanIdApiResponseTimeMsDurationMsDbSystemDbOperationTable
The API is instrumented with the Elastic APM .NET agent.
- APM Server default endpoint:
http://localhost:8200 - Service name:
product-service-api - Main configuration key:
ElasticApm
Docker environment variables:
ELASTICSEARCH_URLELASTIC_APM_SERVER_URL
Request correlation:
- Response header:
X-Transaction-Id - Log field:
TransactionId - If
X-Transaction-Idis provided by the client, the API reuses it. - If it is not provided, the API generates a random 7-digit value per request.
dotnet test ProductService.slnThe current tests focus on the application layer:
- returning all products
- returning a product by id
- assigning
IdandCreatedAtduring product creation - passing updated product data to the repository
- passing the product id to the repository for deletion
The Postman collection is available at:
ProductService.postman_collection.json
Import that file into Postman and update the baseUrl variable if needed.
- The health check endpoint is available at
/health. - Swagger is enabled when the environment is
Development. - The repository uses Dapper with direct SQL queries against the
productstable.