# Kött Gård — Clean Architecture Guide This document explains **what we changed**, **why**, and **how to work in the new structure**. --- ## What is Clean Architecture? Clean Architecture splits code into **layers**. Inner layers hold business rules. Outer layers hold frameworks (React, Next.js, Zustand, localStorage). **Golden rule:** Dependencies point **inward only**. ``` Presentation → Application → Domain Infrastructure → Application → Domain Domain imports NOTHING from outside itself. ``` --- ## Before vs After ### Before (mixed responsibilities) ``` page.tsx → filter products, calculate delivery fee, create orders store/cart.ts → persistence + merge logic + totals lib/products.ts → data + queries in one file ``` Problems: - Delivery fee `total > 500 ? 0 : 49` was copy-pasted in cart **and** checkout - Shop filtering logic lived inside the React page - Hard to swap localStorage for a real API later ### After (separated layers) | Layer | Folder | Job | |-------|--------|-----| | **Domain** | `src/domain/` | Business rules — cart math, delivery fee, order IDs | | **Application** | `src/application/` | Use cases + ports (interfaces) | | **Infrastructure** | `src/infrastructure/` | Zustand, product data file, i18n adapter | | **Presentation** | `src/app/`, `src/components/`, `src/presentation/hooks/` | UI only — calls use cases | --- ## Layer diagram ```mermaid flowchart TB subgraph Presentation P1[app/pages] P2[components] P3[presentation/hooks] end subgraph Application A1[use-cases] A2[ports interfaces] A3[container.ts] end subgraph Domain D1[entities] D2[domain services] end subgraph Infrastructure I1[Zustand stores] I2[InMemoryProductRepository] I3[products.data.ts] end P1 --> P3 P3 --> A3 A3 --> A1 A1 --> A2 A1 --> D2 A2 -.implemented by.- I1 A2 -.implemented by.- I2 I2 --> I3 D2 --> D1 ``` --- ## Step-by-step: what we did ### Step 1 — Domain entities (`src/domain/entities/`) **What:** Moved `Product`, `CartItem`, `Order`, `User`, etc. from `types/` into the domain. **Why:** Entities are the core vocabulary of the business. They must not depend on React or Next.js. **Example:** `Product` describes a meat item (price, category, slug) — not how it is rendered. ``` src/domain/entities/index.ts ``` `src/types/index.ts` now **re-exports** domain entities so old imports still work. --- ### Step 2 — Domain services (`src/domain/services/`) **What:** Pure functions / classes for business rules. | Service | Rule extracted from | |---------|---------------------| | `CartDomainService` | `store/cart.ts` — add item, merge duplicates, totals | | `DeliveryFeeService` | `cart/page.tsx` + `checkout/page.tsx` — 49 kr fee, free over 500 kr | | `CustomizationDomainService` | `lib/customization.ts` — cut styles, cart line keys | | `OrderDomainService` | `checkout/page.tsx` — order ID format, order object shape | | `AuthDomainService` | `store/auth.ts` — demo password check, user creation | | `CatalogDomainService` | `shop/page.tsx` — filter by category, search, sort | **Why:** One place per rule. Change delivery threshold once → cart and checkout both update. **Example — delivery fee (single source of truth):** ```typescript // src/domain/constants/commerce.ts export const FREE_DELIVERY_THRESHOLD_SEK = 500; export const STANDARD_DELIVERY_FEE_SEK = 49; // src/domain/services/DeliveryFeeService.ts static calculateDeliveryFee(subtotal: number): number { return subtotal > FREE_DELIVERY_THRESHOLD_SEK ? 0 : STANDARD_DELIVERY_FEE_SEK; } ``` --- ### Step 3 — Application ports (`src/application/ports/`) **What:** TypeScript **interfaces** describing what the app needs — not how it is stored. | Port | Contract | |------|----------| | `IProductRepository` | `findAll()`, `findBySlug()`, `findFeatured()` | | `ICartRepository` | `getItems()`, `addItem()`, `clearCart()` | | `IAuthRepository` | `login()`, `register()`, `addOrder()` | | `IWishlistRepository` | `toggleItem()`, `isInWishlist()` | | `ITranslationService` | `translate(key)` | **Why:** Today products live in a static array. Tomorrow they might come from Shopify or a database. **Only the infrastructure adapter changes** — use cases stay the same. --- ### Step 4 — Use cases (`src/application/use-cases/`) **What:** One class per user action. Orchestrates domain + ports. | Use case | Replaces logic in | |----------|-------------------| | `FilterProductsUseCase` | Shop page filtering/sorting | | `GetProductBySlugUseCase` | `getProductBySlug()` calls | | `LocalizeProductUseCase` | `lib/product-i18n.ts` | | `AddToCartUseCase` | Cart add button | | `GetCartSummaryUseCase` | Cart totals + delivery | | `PlaceOrderUseCase` | Checkout submit handler | | `LoginUseCase` / `RegisterUseCase` | Login page | **Example — checkout flow:** ``` User clicks Pay → PlaceOrderUseCase.execute() → OrderDomainService.createOrder() [domain] → authRepository.addOrder() [port] → cartRepository.clearCart() [port] ``` File: `src/application/use-cases/checkout/PlaceOrder.ts` --- ### Step 5 — Infrastructure (`src/infrastructure/`) **What:** Concrete implementations of ports + framework code. | File | Role | |------|------| | `data/products.data.ts` | Static 16-product catalog | | `repositories/InMemoryProductRepository.ts` | Implements `IProductRepository` | | `persistence/zustand/cartStore.ts` | Zustand + `ZustandCartRepository` | | `persistence/zustand/authStore.ts` | Auth persistence + demo user | | `persistence/zustand/wishlistStore.ts` | Wishlist persistence | | `i18n/I18nTranslationService.ts` | Wraps existing `i18n/` dictionaries | **Why Zustand stays:** It is an **infrastructure detail** (browser storage). The application layer only sees `ICartRepository`. --- ### Step 6 — Composition root (`src/application/container.ts`) **What:** One file that wires everything together. ```typescript export const container = new ApplicationContainer(); // container.addToCart.execute(...) // container.getCartSummary.execute() // container.placeOrder.execute(...) ``` **Why:** Pages and hooks do not `new InMemoryProductRepository()` themselves. That would couple UI to infrastructure. The container is the **only** place that knows concrete classes. --- ### Step 7 — Presentation hooks (`src/presentation/hooks/`) **What:** React-friendly API on top of the container. | Hook | Use in | |------|--------| | `useCart()` | Cart page, Header, product page | | `useAuth()` | Login, account, checkout | | `useWishlist()` | Product card, wishlist page | | `useCheckout()` | Checkout submit | | `useCatalogFilter()` | Shop page | **Example:** ```typescript // Old (page knew about Zustand internals + business math) const { getTotal } = useCartStore(); const deliveryFee = total > 500 ? 0 : 49; // New (page uses use case result) const { summary } = useCart(); const { subtotal, deliveryFee, grandTotal } = summary; ``` --- ### Step 8 — Backward-compatible facades Old paths still work so nothing breaks silently: | Old import | Now points to | |------------|---------------| | `@/types` | `@/domain/entities` | | `@/lib/customization` | `CustomizationDomainService` | | `@/lib/products` | `container.productRepository` | | `@/store/cart` | infrastructure + `useCart` hook | --- ## New folder map ``` src/ ├── domain/ │ ├── entities/ # Product, Order, CartItem… │ ├── constants/ # FREE_DELIVERY_THRESHOLD_SEK │ └── services/ # CartDomainService, DeliveryFeeService… │ ├── application/ │ ├── ports/ # IProductRepository, ICartRepository… │ ├── use-cases/ # AddToCart, PlaceOrder, FilterProducts… │ ├── dtos/ # LocalizedProduct, CartSummary │ └── container.ts # ★ wires everything │ ├── infrastructure/ │ ├── data/ # products.data.ts, demo-orders.data.ts │ ├── repositories/ # InMemoryProductRepository │ ├── persistence/zustand/ # Stores + repository adapters │ └── i18n/ # I18nTranslationService │ ├── presentation/ │ └── hooks/ # useCart, useAuth, useCatalog… │ ├── app/ # Thin pages (UI + hooks) ├── components/ # Visual components ├── hooks/useTranslation.ts # Still used for t('key') ├── lib/ # Facades (backward compat) ├── store/ # Re-exports (backward compat) └── types/ # Re-exports (backward compat) ``` --- ## How to add a feature (cheat sheet) ### Change delivery fee rule 1. Edit `src/domain/constants/commerce.ts` 2. Done — `GetCartSummaryUseCase` and `PlaceOrderUseCase` pick it up automatically ### Add a new product 1. Add entry in `src/infrastructure/data/products.data.ts` 2. Add translations in `src/i18n/locales/*.ts` ### Add a new page action (e.g. "apply coupon") 1. Add rule in `src/domain/services/` if it is business logic 2. Add port method if it needs storage 3. Create `src/application/use-cases/cart/ApplyCoupon.ts` 4. Register in `container.ts` 5. Expose via `useCart()` or new hook 6. Call from page/component ### Replace static products with an API 1. Create `src/infrastructure/repositories/ApiProductRepository.ts` implementing `IProductRepository` 2. In `container.ts`, swap `InMemoryProductRepository` → `ApiProductRepository` 3. **No changes** to shop page, use cases, or domain --- ## Dependency rules (memorize this) | Layer | Can import | |-------|------------| | Domain | Only domain | | Application | Domain + its own ports/DTOs | | Infrastructure | Application ports + Domain | | Presentation | Application container + hooks + React | | Layer | Cannot import | |-------|---------------| | Domain | React, Next, Zustand, `app/`, `components/` | | Application | Zustand, `page.tsx`, JSX | | Use cases | Concrete repositories (only interfaces) | --- ## Request flow example: Add to cart ```mermaid sequenceDiagram participant Page as product/page.tsx participant Hook as useCart() participant UC as AddToCartUseCase participant Repo as ZustandCartRepository participant Domain as CartDomainService participant Store as Zustand persist Page->>Hook: addItem(product, customization, label) Hook->>UC: execute(...) UC->>Repo: addItem(...) Repo->>Domain: addItem(items, product, ...) Domain-->>Repo: newItems[] Repo->>Store: setItems(newItems) Store-->>Page: React re-render ``` --- ## Verify the project ```bash cd /Users/apple/Desktop/code/Kottgard npm run build # must pass npm run dev # test at http://localhost:3000 ``` Demo login: `demo@kottgard.se` / `demo123` --- ## Further reading - Uncle Bob — Clean Architecture (concentric circles diagram) - This project's annotated code: `docs/annotated/src/` - Beginner folder map: `~/Desktop/Kottgard-Project-Learn/`