Skip to content

glendoci/CheckoutKata

Repository files navigation

Checkout Kata

A .NET implementation of the classic supermarket checkout kata, demonstrating clean architecture, SOLID principles, and extensible pricing strategies.

Problem Statement

Implement a supermarket checkout that:

  • Scans items individually (identified by SKU)
  • Calculates running totals
  • Applies special offers (e.g., "3 for 130", "buy 2 get 1 free")
  • Accepts items in any order
  • Supports configurable pricing rules

Architecture

graph TB
    subgraph "Application Layer"
        ICheckout[ICheckout]
        IPricingService[IPricingService]
        CheckOutService[CheckOutService]
        PricingService[PricingService]
    end

    subgraph "Domain Layer"
        Sku[Sku]
        ProductPricing[ProductPricing]
        SpecialOffer[SpecialOffer]

        subgraph "Special Offers"
            MultiPriceOffer[MultiPriceOffer<br/>3 for 130]
            BuyXGetYFree[BuyXGetYFreeOffer<br/>Buy 2 Get 1 Free]
            ThresholdPercent[ThresholdQuantityPercentOffer<br/>10% off when buying 5+]
            TieredPrice[TieredUnitPriceOffer<br/>Volume discounts]
        end
    end

    CheckOutService --> ICheckout
    CheckOutService --> PricingService
    PricingService --> IPricingService
    PricingService --> ProductPricing
    ProductPricing --> Sku
    ProductPricing --> SpecialOffer

    MultiPriceOffer --> SpecialOffer
    BuyXGetYFree --> SpecialOffer
    ThresholdPercent --> SpecialOffer
    TieredPrice --> SpecialOffer
Loading

Project Structure

CheckoutKata/
├── CheckoutKata.Domain/           # Pure domain models (no dependencies)
│   ├── Sku.cs                     # Value object for product identifier
│   ├── ProductPricing.cs          # Product with price and optional offer
│   └── SpecialOffers/
│       ├── SpecialOffer.cs        # Abstract base (Strategy pattern)
│       ├── MultiPriceOffer.cs     # "3 for 130"
│       ├── BuyXGetYFreeOffer.cs   # "Buy 2 get 1 free"
│       ├── ThresholdQuantityPercentOffer.cs  # "10% off 5+"
│       └── TieredUnitPriceOffer.cs           # Volume pricing
│
├── CheckoutKata.Application/      # Use cases and services
│   ├── Interfaces/
│   │   ├── ICheckout.cs           # Checkout contract
│   │   └── IPricingService.cs     # Pricing calculation contract
│   └── Services/
│       ├── CheckOutService.cs     # Stateful checkout implementation
│       └── PricingService.cs      # Pricing calculation engine
│
└── CheckoutKata.UnitTests/        # Comprehensive test suite
    ├── CheckoutTestBase.cs        # Shared test infrastructure
    ├── Catalogs/                  # Test pricing configurations
    └── Tests/
        ├── CheckoutTests.cs       # Core kata tests
        ├── SpecialOffersCheckoutTests.cs  # Extended offer tests
        └── InvalidInputTests.cs   # Validation tests

Pricing Rules (Default Kata)

SKU Unit Price Special Offer
A 50 3 for 130
B 30 2 for 45
C 20 -
D 15 -

Getting Started

Prerequisites

Run Tests

dotnet test

Run Tests with Verbosity

dotnet test --verbosity normal

Build Only

dotnet build

Usage Example

// Create pricing catalog
var products = new[]
{
    new ProductPricing(new Sku("A"), 50, new MultiPriceOffer(3, 130)),
    new ProductPricing(new Sku("B"), 30, new MultiPriceOffer(2, 45)),
    new ProductPricing(new Sku("C"), 20),
    new ProductPricing(new Sku("D"), 15)
};

// Create checkout
var checkout = new CheckOutService(products);

// Scan items (any order)
checkout.Scan("A");
checkout.Scan("B");
checkout.Scan("A");
checkout.Scan("A");  // 3 A's now qualify for special

// Get total (applies offers automatically)
var total = checkout.GetTotalPrice();  // 130 + 30 = 160

Extensibility

Adding a new offer type is simple:

  1. Create a class extending SpecialOffer
  2. Implement CalculateLineTotal
  3. Use it in your product catalog
public class MyCustomOffer : SpecialOffer
{
    public override int CalculateLineTotal(
        ProductPricing product,
        int quantity,
        IReadOnlyDictionary<Sku, int> basketQuantities)
    {
        // Your pricing logic here
    }
}

Design Decisions

  • Strategy Pattern: Special offers are interchangeable pricing strategies
  • Value Objects: Sku ensures consistent identity (trimmed, uppercase)
  • Separation of Concerns: Domain logic is isolated from application services
  • Fail Fast: Guard clauses validate inputs immediately
  • Immutable Configuration: Pricing rules are set at construction

Test Coverage

  • 32 tests covering:
    • Single items and combinations
    • All special offer types
    • Order-independent scanning
    • Incremental totals
    • Input validation and edge cases

About

.NET implementation of the supermarket checkout kata

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages