Skip to main content

Overview

The Product class represents a food or beverage item that can be ordered by customers. Each product has a name and a type that determines which kitchen station will prepare it. Products are immutable value objects in the domain model.
Product follows the Value Object pattern from Domain-Driven Design. Two products with the same name and type are considered equal.

Class Definition

package com.foodtech.kitchen.domain.model;

public class Product {
    private final String name;
    private final ProductType type;

    public Product(String name, ProductType type) {
        validate(name, type);
        this.name = name;
        this.type = type;
    }
}

Fields

name
String
required
The name of the product (e.g., “Caesar Salad”, “Grilled Steak”, “Mojito”). Cannot be null or blank.
type
ProductType
required
The type of product, which determines the station that will prepare it. Must be one of: DRINK, HOT_DISH, or COLD_DISH.

Constructor

public Product(String name, ProductType type)
Creates a new Product instance. Parameters:
  • name - The product name (required, cannot be null or blank)
  • type - The product type (required, cannot be null)
Throws:
  • IllegalArgumentException if name is null or blank
  • IllegalArgumentException if type is null
The name is checked with isBlank(), which returns true for empty strings and strings containing only whitespace.

Validation Rules

The Product class enforces the following validation:
FieldValidationException
NameCannot be null or blank (empty/whitespace)IllegalArgumentException
TypeCannot be nullIllegalArgumentException

Validation Implementation

private void validate(String name, ProductType type) {
    if (name == null || name.isBlank()) {
        throw new IllegalArgumentException("Product name cannot be null or empty");
    }
    if (type == null) {
        throw new IllegalArgumentException("Product type cannot be null");
    }
}

Public Methods

getName()

public String getName()
Returns the product name.

getType()

public ProductType getType()
Returns the product type, which determines the assigned station.

ProductType Enum

The ProductType enum defines the three types of products and their associated stations:
public enum ProductType {
    DRINK(Station.BAR),
    HOT_DISH(Station.HOT_KITCHEN),
    COLD_DISH(Station.COLD_KITCHEN);

    private final Station station;

    ProductType(Station station) {
        this.station = station;
    }

    public Station getStation() {
        return station;
    }
}

Product Type Mappings

ProductTypeStationExamples
DRINKBARMojito, Lemonade, Coffee, Beer
HOT_DISHHOT_KITCHENGrilled Steak, Pasta, Soup
COLD_DISHCOLD_KITCHENCaesar Salad, Ceviche, Carpaccio

getStation()

Each ProductType knows its assigned station:
ProductType type = ProductType.DRINK;
Station station = type.getStation(); // Returns Station.BAR
This design follows the Open-Closed Principle. Each ProductType encapsulates its station mapping, eliminating the need for a separate mapper class.

Usage Examples

Creating Products

// Creating different types of products
Product salad = new Product("Caesar Salad", ProductType.COLD_DISH);
Product steak = new Product("Grilled Steak", ProductType.HOT_DISH);
Product mojito = new Product("Mojito", ProductType.DRINK);

// Accessing product information
String name = salad.getName();        // "Caesar Salad"
ProductType type = salad.getType();   // ProductType.COLD_DISH
Station station = type.getStation();  // Station.COLD_KITCHEN

Validation Examples

// Valid products
Product valid1 = new Product("Pizza", ProductType.HOT_DISH); // OK
Product valid2 = new Product("   Soup   ", ProductType.HOT_DISH); // OK (spaces are valid)

// Invalid products - will throw IllegalArgumentException
try {
    Product invalid1 = new Product(null, ProductType.DRINK);
} catch (IllegalArgumentException e) {
    // "Product name cannot be null or empty"
}

try {
    Product invalid2 = new Product("", ProductType.DRINK);
} catch (IllegalArgumentException e) {
    // "Product name cannot be null or empty"
}

try {
    Product invalid3 = new Product("   ", ProductType.DRINK);
} catch (IllegalArgumentException e) {
    // "Product name cannot be null or empty" (isBlank() catches whitespace)
}

try {
    Product invalid4 = new Product("Pizza", null);
} catch (IllegalArgumentException e) {
    // "Product type cannot be null"
}

Determining Station Assignment

// Products automatically know their station through their type
Product drink = new Product("Beer", ProductType.DRINK);
Station barStation = drink.getType().getStation(); // Station.BAR

Product hotFood = new Product("Pasta", ProductType.HOT_DISH);
Station hotKitchen = hotFood.getType().getStation(); // Station.HOT_KITCHEN

Product coldFood = new Product("Sushi", ProductType.COLD_DISH);
Station coldKitchen = coldFood.getType().getStation(); // Station.COLD_KITCHEN

Grouping Products by Station

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

List<Product> products = List.of(
    new Product("Mojito", ProductType.DRINK),
    new Product("Caesar Salad", ProductType.COLD_DISH),
    new Product("Grilled Steak", ProductType.HOT_DISH),
    new Product("Beer", ProductType.DRINK)
);

// Group products by their station
Map<Station, List<Product>> byStation = products.stream()
    .collect(Collectors.groupingBy(p -> p.getType().getStation()));

// Result:
// Station.BAR -> [Mojito, Beer]
// Station.COLD_KITCHEN -> [Caesar Salad]
// Station.HOT_KITCHEN -> [Grilled Steak]

Design Patterns and Principles

Value Object Pattern

Product is immutable with all fields final. It represents a value rather than an entity with identity.

Open-Closed Principle

ProductType encapsulates station mapping within the enum itself. Adding a new product type doesn’t require modifying other classes.

Fail-Fast Validation

All validation happens in the constructor, ensuring invalid products cannot exist in the system.

Architecture Notes

Why ProductType Contains Station

The implementation includes a design comment explaining an architectural decision:
//HUMAN REVIEW: Agregué campo station a ProductType para eliminar violación OCP.
//Ahora cada ProductType conoce su Station, eliminando necesidad de ProductStationMapper.
//Cumple OCP: agregar nuevo tipo no requiere modificar otras clases.
This design choice:
  • Eliminates the need for a separate ProductStationMapper class
  • Follows OCP (Open-Closed Principle) - new product types can be added without modifying existing code
  • Encapsulates the station mapping logic within the enum itself
  • Reduces coupling between domain classes
This is an example of refactoring toward better adherence to SOLID principles. The station mapping is now a property of the product type itself, not an external mapping.
  • ProductType - Enum defining product categories
  • Station - Kitchen stations that prepare products
  • Order - Contains lists of products
  • Task - Contains products filtered by station

Build docs developers (and LLMs) love