Skip to content

fredericbrass/shopping-basket-api

Repository files navigation

Shopping Basket API

A REST API for managing an online shopping basket with CQRS architecture - using DDD principles.

Please refer to the Refactorings Documentation for details on how I would extend and improve this API beyond the scope of this technical exercise.

Features implemented

  • Add an item to the basket
  • Add multiple items to the basket
  • Remove an item from the basket
  • Add multiple of the same item to the basket
  • Get the total cost for the basket (including 20% VAT)
  • Get the total cost without VAT
  • Add a discounted item to the basket
  • Add a discount code to the basket (excluding discounted items)
  • Add shipping cost to the UK
  • Add shipping cost to other countries

Architecture

This API follows Clean Architecture principles with CQRS (Command Query Responsibility Segregation) pattern and Domain-Driven Design (DDD) concepts.

Key Architectural Patterns

  • CQRS: Separates read (Query) and write (Command) operations for better scalability and maintainability
  • DDD: Domain entities encapsulate business logic, ensuring a rich domain model
  • Repository Pattern: Abstracts data access logic from business logic
  • Dependency Injection: Promotes loose coupling and testability
  • Clean Architecture: Dependencies flow inward toward the domain layer

Project Structure

shopping-basket-api/
├── ShoppingBasket.Api/                 # Presentation Layer
│   ├── Controllers/                    # API endpoints
│   ├── Models/                         # Request/Response models
│   └── Program.cs                      # Application entry point
│
├── ShoppingBasket.Application/         # Application Layer
│   ├── Command/
│   │   ├── Commands/                   # Write operations
│   │   └── Handlers/                   # Command handlers
│   ├── Query/
│   │   ├── Queries/                    # Read operations
│   │   └── Handlers/                   # Query handlers
│   ├── DTOs/                           # Data Transfer Objects
│   ├── Mappers/                        # Entity to DTO mapping
│   └── Interfaces/                     # Application contracts
│
├── ShoppingBasket.Domain/              # Domain Layer
│   ├── Entities/                       # Domain models
│   │   ├── Basket.cs                   # Aggregate root
│   │   └── BasketItem.cs               # Entity
│   └── Interfaces/                     # Domain contracts
│
├── ShoppingBasket.Infrastructure/      # Infrastructure Layer
│   ├── Data/
│   │   ├── ApplicationDbContext.cs     # EF Core context
│   │   └── Migrations/                 # Database migrations
│   └── Repositories/                   # Data access implementations
│       ├── BasketRepository.cs
│       └── Interfaces/
│
├── ShoppingBasket.UnitTests/           # Unit Tests
│   ├── Domain/                         # Domain logic tests
│   ├── Application/                    # Handler tests
│   └── Api/                            # Controller tests
│
├── docs/                               # Documentation
│   └── refactorings.md                 # Future improvements
│
├── docker-compose.yml                  # Docker orchestration
├── Dockerfile                          # Container definition
└── README.md                           # This file

Technology Stack

  • .NET 8: Latest LTS version of .NET
  • Entity Framework Core: ORM for data access
  • SQL Server: Relational database
  • xUnit: Unit testing framework
  • Docker: Containerization
  • Swagger/OpenAPI: API documentation
  • CQRS: Command/Query separation (custom implementation)

Design Decisions

Why CQRS?

CQRS allows us to:

  • Optimize read and write operations independently
  • Scale read and write workloads separately
  • Maintain clear separation of concerns
  • Simplify complex business logic

Why DDD?

Domain-Driven Design helps us:

  • Keep business logic in the domain layer
  • Create a rich, expressive domain model
  • Maintain consistency through aggregate roots
  • Ensure entities are always in a valid state

Prerequisites

  • Docker Desktop (optional)
  • .NET 8 SDK
  • SQL Server or LocalDB
  • Git

Running the Application

Option 1: Docker (Recommended)

  1. Clone the repository
  2. Navigate to the project root directory
  3. Run the following command:

For Docker Desktop (latest) or Docker Compose V2:

docker compose up --build

The API will be available at: http://localhost:5000

The database will automatically initialize with migrations on startup.

Option 2: Local Development (without Docker)

  1. Install SQL Server or SQL Server Express
  2. Update connection string in appsettings.json:
"ConnectionStrings": {
  "DefaultConnection": "Server=localhost;Database=ShoppingBasketDb;Trusted_Connection=True;TrustServerCertificate=True;"
}
  1. Run migrations:
dotnet ef database update --project ShoppingBasket.Infrastructure --startup-project ShoppingBasket.Api
  1. Start the API:
dotnet run --project ShoppingBasket.Api

The API will be available at: https://localhost:7000 or http://localhost:5000

API Usage

Swagger Documentation

Once the application is running, you can access the interactive API documentation at:

  • http://localhost:5000/swagger (Docker)
  • https://localhost:7000/swagger (Local development)

Available Endpoints

Baskets

Create a new basket

POST /api/baskets

Get basket by ID

GET /api/baskets/{basketId}

Get basket total

GET /api/baskets/{basketId}/total

Basket Items

Add items to basket

POST /api/baskets/{basketId}/items
Content-Type: application/json

[
  {
    "name": "Product Name",
    "price": 29.99,
    "quantity": 2,
    "isDiscounted": false
  }
]

Remove item from basket

DELETE /api/baskets/{basketId}/items/{basketItemId}

Apply discount code to basket

POST /api/baskets/{basketId}/discountcode
Content-Type: application/json

{
  "discountCode": "SAVE10"
}

Example Usage Flow

  1. Create a new basket:
curl -X POST http://localhost:5000/api/baskets
  1. Add items to the basket:
curl -X POST http://localhost:5000/api/baskets/{basketId}/items \
  -H "Content-Type: application/json" \
  -d '[
    {
      "name": "Laptop",
      "price": 999.99,
      "quantity": 1,
      "isDiscounted": false
    },
    {
      "name": "Mouse",
      "price": 29.99,
      "quantity": 2,
      "isDiscounted": true
    }
  ]'
  1. Apply a discount code:
  • The only accepted discount codes are 'SAVE10', 'SAVE20' and 'SAVE30'.
curl -X POST http://localhost:5000/api/baskets/{basketId}/discountcode \
  -H "Content-Type: application/json" \
  -d '{"discountCode": "SAVE10"}'
  1. Retrieve the basket:
curl -X GET http://localhost:5000/api/baskets/{basketId}
  1. Get basket total:
curl -X GET http://localhost:5000/api/baskets/{basketId}/total
  1. Remove an item:
curl -X DELETE http://localhost:5000/api/baskets/{basketId}/items/{basketItemId}

Response Format

Create Basket Response:

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "createdAt": "2024-10-20T10:30:00Z",
  "updatedAt": "2024-10-20T10:30:00Z",
  "discountCode": null,
  "discountPercentage": 0.0,
  "items": [],
  "total": {
    "totalExcludingVat": 0.0,
    "totalIncludingVat": 0.0,
    "vatAmount": 0.0,
    "discountAmount": 0.0
  }
}

Get Basket Response:

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "createdAt": "2024-10-20T10:30:00Z",
  "updatedAt": "2024-10-20T10:35:00Z",
  "discountCode": null,
  "discountPercentage": 0.0,
  "items": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174002",
      "basketId": "123e4567-e89b-12d3-a456-426614174000",
      "productName": "Laptop",
      "quantity": 1,
      "unitPrice": 999.99,
      "addedAt": "2024-10-20T10:32:00Z"
    },
    {
      "id": "123e4567-e89b-12d3-a456-426614174003",
      "basketId": "123e4567-e89b-12d3-a456-426614174000",
      "productName": "Mouse",
      "quantity": 2,
      "unitPrice": 29.99,
      "addedAt": "2024-10-20T10:33:00Z"
    }
  ],
  "total": {
    "totalExcludingVat": 1059.97,
    "totalIncludingVat": 1271.96,
    "vatAmount": 211.99,
    "discountAmount": 0.0
  }
}

Add Items Response:

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "createdAt": "2024-10-20T10:30:00Z",
  "updatedAt": "2024-10-20T10:35:00Z",
  "discountCode": null,
  "discountPercentage": 0.0,
  "items": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174002",
      "basketId": "123e4567-e89b-12d3-a456-426614174000",
      "productName": "Laptop",
      "quantity": 1,
      "unitPrice": 999.99,
      "addedAt": "2024-10-20T10:32:00Z"
    }
  ],
  "total": {
    "totalExcludingVat": 999.99,
    "totalIncludingVat": 1199.99,
    "vatAmount": 200.0,
    "discountAmount": 0.0
  }
}

Apply Discount Code Response:

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "createdAt": "2024-10-20T10:30:00Z",
  "updatedAt": "2024-10-20T10:40:00Z",
  "discountCode": "SAVE10",
  "discountPercentage": 10.0,
  "items": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174002",
      "basketId": "123e4567-e89b-12d3-a456-426614174000",
      "productName": "Laptop",
      "quantity": 1,
      "unitPrice": 999.99,
      "addedAt": "2024-10-20T10:32:00Z"
    }
  ],
  "total": {
    "totalExcludingVat": 899.99,
    "totalIncludingVat": 1079.99,
    "vatAmount": 180.0,
    "discountAmount": 100.0
  }
}

Get Total Response:

{
  "totalExcludingVat": 899.99,
  "totalIncludingVat": 1079.99,
  "vatAmount": 180.0,
  "discountAmount": 100.0
}

Error Response Example:

{
  "error": "Basket ID must not be empty"
}

HTTP Status Codes

  • 200 OK - Successful GET requests
  • 201 Created - Successful basket creation
  • 204 No Content - Successful item removal
  • 400 Bad Request - Invalid request data or validation errors
  • 404 Not Found - Basket not found
  • 500 Internal Server Error - Server error

Known Limitations

Given the 4-hour development timeframe:

  • No authentication/authorization implemented
  • Limited error handling and validation
  • No rate limiting
  • Basic logging implementation
  • See refactorings.md for production-ready improvements

License

This project is part of a technical assessment and is not licensed for external use.

About

Shopping Basket API Application - Freemarket

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published