- Summary
- 1. About this project
- 2. Screenshots or Demo
- 3. Getting started
- 4. Functionalities
- 5. Implementation details
- 6. Contributing
- 7. License
Ecommerce is a personal portfolio project built in ASP.NET Core to practice new trends and technologies in modern backend development. It is UNDER CONSTRUCTION and intentionally evolutionary, shipped today as a modular monolith open for expansion. The project offers hands-on experience with modern tools, patterns and methodologies, promoting growth and adaptability, exploring efficient coding practices, clear architectural decisions and project management skills that enhance my ability to deliver high-quality software solutions.
Coming soon.
- .NET 10 SDK
- Docker with Docker Compose
-
Clone the repository.
-
Create a
.envfile undersrc/from the provided example:cd src cp .env.example .envEdit
.envand setPOSTGRES_PASSWORD(and the matching password insideConnectionStrings__EcommerceDb) to something other than the default. -
Bring the stack up:
docker compose up --build
This boots two containers:
ecommerce-dbβ PostgreSQL 17 with a persistent volume.ecommerce-apiβ the API (Ecommerce.AppHost) listening onhttp://localhost:8080andhttps://localhost:8081.
EF Core migrations are applied on startup so the database is ready as soon as the API answers.
-
Open the Scalar UI at
http://localhost:8080/scalar/v1to explore endpoints.
cd src
dotnet testIntegration tests require Docker to be running. See 5.4 Integration Tests for the composition.
Each module groups one or more features. Cross-cutting items are not tied to a single module.
| Id | Module | Feature | Status |
|---|---|---|---|
| 1 | Catalog | Category Management | π’ Done |
| 2 | Catalog | Product Management | π΄ To do |
| 3 | Auth | β | π‘ In progress |
| 5 | Orders | β | π΄ To do |
| 6 | Shipping | β | π΄ To do |
| 7 | Payment (Microservice) | β | π΄ To do |
| 8 | Notifications | β | π΄ To do |
| 9 | Cross-cutting | Request Validation | π’ Done |
| 10 | Cross-cutting | Global Exception Handling | π’ Done |
| 11 | Cross-cutting | API Documentation | π’ Done |
| 12 | Cross-cutting | CI/CD (GitHub Actions) | π‘ In progress |
| 13 | Cross-cutting | Deployment & Environments | π΄ To do |
| 14 | Cross-cutting | Observability | π΄ To do |
| 15 | Cross-cutting | Rate Limiting | π΄ To do |
| 16 | Cross-cutting | Domain Validation Rules | π΄ To do |
| 17 | Cross-cutting | Integration Tests | π’ Done |
This section expands on the Functionalities table β pick a row above and find it here for the technologies, patterns and reasoning behind it.
- .NET 10 / ASP.NET Core 10 / C# β API runtime and framework.
- PostgreSQL 17 with Entity Framework Core β relational store, migrations and data access.
- MediatR β CQRS dispatch for commands and queries.
- FluentValidation β declarative request validation.
- Scalar UI (over OpenAPI) β interactive API documentation.
- xUnit, NSubstitute, Bogus, Shouldly, Testcontainers, Respawner β testing toolchain.
- Docker + Docker Compose β containerization.
- GitHub Actions β CI (build, unit tests, integration tests, Docker image validation, commit message linting).
- Azure β target cloud for deployment (App Service / Container Apps + Azure Database for PostgreSQL).
Ecommerce.AppHost is the composition root. Each module ships an Api project that exposes two extension methods β
Add{Module}Module(IServiceCollection, IConfiguration) and Use{Module}Module(IApplicationBuilder) β both invoked
uniformly by ModulesRegistry.AddModules / RegisterModules.
Modules never reference each other directly: cross-module communication goes through IModule in
Ecommerce.Kernel.Application and per-module contracts in
{Module}.Application (e.g. ICatalogModule in Ecommerce.Catalog.Application); the implementing module ships an
internal mediator-backed adapter that extends MediatorModuleBase so consumers see a typed contract instead of
ISender.
flowchart LR
Req[HTTP Request] --> Filter[RequestValidationFilter]
Filter -- valid --> Ctrl[Controller / Handler]
Filter -- invalid --> PDW[ProblemDetailsWriter<br/>400 ValidationProblemDetails]
Ctrl -- IExceptionContract thrown --> Handler[GlobalExceptionHandler]
Ctrl -- unhandled exception --> Handler
Handler -- has IExceptionContract --> PDW2[ProblemDetailsWriter<br/>StatusCode + Detail]
Handler -- no contract --> PDW3[ProblemDetailsWriter<br/>500 generic]
Ctrl -- success --> Res[HTTP Response]
PDW --> Res
PDW2 --> Res
PDW3 --> Res
Two complementary paths produce a RFC 7807 ProblemDetails response β
one for invalid input, one for thrown exceptions. Both go through a single writer (ProblemDetailsWriter) so the
response shape stays consistent.
- Request body validation.
RequestValidationFilteris registered globally inApiModule.AddApiModule. For each request DTO, it resolves the matchingIValidator<T>from DI, callsValidateAsync, and on failure short-circuits the pipeline with a400 ValidationProblemDetailswritten byProblemDetailsWriterβ the controller action never runs. - Controlled exceptions. Handlers throw exceptions that implement
IExceptionContract(e.g.ResourceNotFoundExceptionβ404,BusinessRuleValidationExceptionβ409).GlobalExceptionHandler(IExceptionHandler) picks up the contract, readsStatusCodeandMessage, and produces aProblemDetailsthrough the same writer. Anything that does not implement the contract falls back to a generic500with no leakage of internal details.
Integration tests run against a real PostgreSQL 17 instance β Testcontainers spins up a container per fixture, EF
migrations are applied through a WebApplicationFactory during host startup, and Respawner clears the schema between
tests so each one starts from a known state. The stack composes a few small pieces, each with one responsibility:
DatabaseContainerFixtureboots apostgres:17container via Testcontainers and exposes its connection string.BaseIntegrationFixture<TFactory>(Kernel) owns the container, instantiates the per-moduleWebApplicationFactory, callsCreateClient(which applies EF migrations during host startup), and then constructs aDatabaseResetterover the schemas the module declares.EcommerceWebApplicationFactoryis an abstract base overWebApplicationFactory<IApiMarker>. ItsConfigureWebHostinjects the container connection string into in-memory configuration so the API points at the test database. Each module supplies a concrete factory (e.g.CatalogWebApplicationFactory).DatabaseResetterwrapsRespawnerwithDbAdapter.Postgresand only the module-owned schemas, giving each test a clean slate viaResetAsync.CatalogTestCollectionis an xUnit[CollectionDefinition]withICollectionFixture<CatalogIntegrationFixture>so the fixture (and its container) is shared across the whole test class set.BaseCatalogIntegrationTesthides the wiring and exposesClient,ResetDatabaseAsync()andSeedAsync<CatalogDbContext>()to test classes.
Three workflows live under .github/workflows/:
ci.ymlruns on every push and on pull requests targetingmain. It runs in two jobs:build-and-unit-tests( restore, build in Release, run only tests whose fully-qualified name containsUnitTests) andintegration-tests( same, but filtering onIntegrationTestsand depending on the first job). NuGet packages are cached by*.csprojhash.docker.ymlbuilds the production image fromsrc/Ecommerce.AppHost/Dockerfileon changes undersrc/**. It does not push β it validates that the image still builds.commitlint.ymllints PR commit messages against the Conventional Commits config at the repo root (commitlint.config.cjs,commitlinterrc.json).
What is still open: deployment workflows (Azure) and a release pipeline.
You can send how many PR's do you want, I'll be glad to analyze and accept them! And if you have any question about the project just ask...
This project is licensed under the MIT License - see the LICENSE.md file for details