+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 432 of 541

๐Ÿ“˜ Domain-Driven Design: Python Implementation

Master domain-driven design: python implementation in Python with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿ’ŽAdvanced
25 min read

Prerequisites

  • Basic understanding of programming concepts ๐Ÿ“
  • Python installation (3.8+) ๐Ÿ
  • VS Code or preferred IDE ๐Ÿ’ป

What you'll learn

  • Understand the concept fundamentals ๐ŸŽฏ
  • Apply the concept in real projects ๐Ÿ—๏ธ
  • Debug common issues ๐Ÿ›
  • Write clean, Pythonic code โœจ

๐ŸŽฏ Introduction

Welcome to this exciting tutorial on Domain-Driven Design (DDD) in Python! ๐ŸŽ‰ In this guide, weโ€™ll explore how to build software that truly reflects your business domain.

Youโ€™ll discover how DDD can transform your Python development experience. Whether youโ€™re building microservices ๐ŸŒ, enterprise applications ๐Ÿ–ฅ๏ธ, or complex business systems ๐Ÿ“š, understanding DDD is essential for writing robust, maintainable code that speaks your business language.

By the end of this tutorial, youโ€™ll feel confident implementing DDD patterns in your own projects! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Domain-Driven Design

๐Ÿค” What is Domain-Driven Design?

Domain-Driven Design is like building a house with blueprints that everyone can understand ๐Ÿ—๏ธ. Think of it as creating software that speaks the same language as your business experts - no translation needed!

In Python terms, DDD is a strategic approach to software design that focuses on modeling software to match a domain. This means you can:

  • โœจ Create code that business experts can understand
  • ๐Ÿš€ Build systems that evolve with business needs
  • ๐Ÿ›ก๏ธ Protect business logic from technical concerns

๐Ÿ’ก Why Use DDD?

Hereโ€™s why developers love DDD:

  1. Ubiquitous Language ๐Ÿ—ฃ๏ธ: Everyone speaks the same language
  2. Bounded Contexts ๐Ÿ“ฆ: Clear boundaries between different parts
  3. Rich Domain Models ๐Ÿ’Ž: Business logic lives where it belongs
  4. Event-Driven Architecture ๐ŸŽฏ: Systems that react to business events

Real-world example: Imagine building an e-commerce platform ๐Ÿ›’. With DDD, your code would use terms like โ€œOrderโ€, โ€œCustomerโ€, and โ€œInventoryโ€ - exactly what the business uses!

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Simple Example: Building Blocks

Letโ€™s start with DDDโ€™s core building blocks:

# ๐Ÿ‘‹ Hello, DDD!
from dataclasses import dataclass
from typing import List, Optional
from datetime import datetime
import uuid

# ๐ŸŽจ Value Object - Immutable and defined by its attributes
@dataclass(frozen=True)
class Money:
    amount: float
    currency: str = "USD"  # ๐Ÿ’ต Default currency
    
    def __add__(self, other: 'Money') -> 'Money':
        if self.currency != other.currency:
            raise ValueError("Cannot add different currencies! ๐Ÿ’”")
        return Money(self.amount + other.amount, self.currency)

# ๐Ÿ—๏ธ Entity - Has identity and lifecycle
class Product:
    def __init__(self, name: str, price: Money):
        self.id = str(uuid.uuid4())  # ๐Ÿ”‘ Unique identity
        self.name = name
        self.price = price
        self.stock = 0
        
    def add_stock(self, quantity: int):
        """๐Ÿ“ฆ Add items to inventory"""
        self.stock += quantity
        print(f"Added {quantity} {self.name}(s) to stock! ๐Ÿ“ˆ")

๐Ÿ’ก Explanation: Notice how we use domain terms directly! Money is a value object (immutable), while Product is an entity (has identity).

๐ŸŽฏ Domain Events

Hereโ€™s how we capture things that happen in our domain:

# ๐ŸŽฏ Domain Event - Something that happened
@dataclass
class ProductPurchased:
    product_id: str
    quantity: int
    price: Money
    purchased_at: datetime
    customer_id: str
    
    def __str__(self):
        return f"๐Ÿ›๏ธ Customer {self.customer_id} bought {self.quantity} items!"

# ๐Ÿš€ Aggregate - Consistency boundary
class ShoppingCart:
    def __init__(self, customer_id: str):
        self.id = str(uuid.uuid4())
        self.customer_id = customer_id
        self.items: List[tuple[Product, int]] = []
        self.events: List[ProductPurchased] = []  # ๐Ÿ“ Event sourcing!
        
    def add_item(self, product: Product, quantity: int):
        """๐Ÿ›’ Add product to cart"""
        if product.stock < quantity:
            raise ValueError(f"Not enough stock! Only {product.stock} available ๐Ÿ˜ข")
            
        self.items.append((product, quantity))
        print(f"Added {quantity} {product.name}(s) to cart! โœจ")
        
    def checkout(self) -> List[ProductPurchased]:
        """๐Ÿ’ณ Complete the purchase"""
        events = []
        for product, quantity in self.items:
            event = ProductPurchased(
                product_id=product.id,
                quantity=quantity,
                price=Money(product.price.amount * quantity),
                purchased_at=datetime.now(),
                customer_id=self.customer_id
            )
            events.append(event)
            product.stock -= quantity  # ๐Ÿ“‰ Update inventory
            
        self.events.extend(events)
        self.items.clear()  # ๐Ÿงน Empty the cart
        return events

๐Ÿ’ก Practical Examples

๐Ÿฆ Example 1: Banking Domain

Letโ€™s build a simple banking system:

# ๐Ÿ’ฐ Banking domain model
from enum import Enum
from decimal import Decimal

class AccountType(Enum):
    CHECKING = "checking"
    SAVINGS = "savings"
    
@dataclass(frozen=True)
class AccountNumber:
    """๐Ÿ”ข Value object for account numbers"""
    value: str
    
    def __post_init__(self):
        if not self.value or len(self.value) != 10:
            raise ValueError("Account number must be 10 digits! ๐Ÿšซ")

class BankAccount:
    """๐Ÿฆ Bank account aggregate"""
    def __init__(self, account_number: AccountNumber, account_type: AccountType):
        self.account_number = account_number
        self.account_type = account_type
        self.balance = Decimal("0.00")
        self.is_active = True
        self.transactions: List[Transaction] = []
        
    def deposit(self, amount: Decimal) -> 'Transaction':
        """๐Ÿ’ต Deposit money"""
        if amount <= 0:
            raise ValueError("Amount must be positive! ๐Ÿ’ธ")
            
        if not self.is_active:
            raise ValueError("Account is closed! ๐Ÿ”’")
            
        self.balance += amount
        transaction = Transaction(
            account_number=self.account_number,
            amount=amount,
            transaction_type="DEPOSIT",
            balance_after=self.balance
        )
        self.transactions.append(transaction)
        print(f"โœ… Deposited ${amount}! New balance: ${self.balance}")
        return transaction
        
    def withdraw(self, amount: Decimal) -> 'Transaction':
        """๐Ÿ’ธ Withdraw money"""
        if amount > self.balance:
            raise ValueError(f"Insufficient funds! Only ${self.balance} available ๐Ÿ˜…")
            
        self.balance -= amount
        transaction = Transaction(
            account_number=self.account_number,
            amount=amount,
            transaction_type="WITHDRAWAL",
            balance_after=self.balance
        )
        self.transactions.append(transaction)
        print(f"๐Ÿ’ณ Withdrew ${amount}! Remaining: ${self.balance}")
        return transaction

@dataclass
class Transaction:
    """๐Ÿ“ Domain event for transactions"""
    account_number: AccountNumber
    amount: Decimal
    transaction_type: str
    balance_after: Decimal
    timestamp: datetime = datetime.now()
    
# ๐ŸŽฎ Let's use our banking domain!
account = BankAccount(
    AccountNumber("1234567890"),
    AccountType.CHECKING
)
account.deposit(Decimal("1000.00"))
account.withdraw(Decimal("50.00"))

๐ŸŽฏ Try it yourself: Add a transfer method that moves money between accounts!

๐Ÿš— Example 2: Ride-Sharing Domain

Letโ€™s model a ride-sharing service:

# ๐Ÿš— Ride-sharing domain
from geopy.distance import geodesic

@dataclass(frozen=True)
class Location:
    """๐Ÿ“ Value object for locations"""
    latitude: float
    longitude: float
    address: str
    
    def distance_to(self, other: 'Location') -> float:
        """๐Ÿ“ Calculate distance in kilometers"""
        return geodesic(
            (self.latitude, self.longitude),
            (other.latitude, other.longitude)
        ).km

class RideStatus(Enum):
    REQUESTED = "๐Ÿ”ต Requested"
    DRIVER_ASSIGNED = "๐ŸŸก Driver Assigned"
    IN_PROGRESS = "๐ŸŸข In Progress"
    COMPLETED = "โœ… Completed"
    CANCELLED = "โŒ Cancelled"

class Ride:
    """๐Ÿš— Ride aggregate"""
    def __init__(self, rider_id: str, pickup: Location, destination: Location):
        self.id = str(uuid.uuid4())
        self.rider_id = rider_id
        self.driver_id: Optional[str] = None
        self.pickup = pickup
        self.destination = destination
        self.status = RideStatus.REQUESTED
        self.price: Optional[Money] = None
        self.events: List[dict] = []
        
        # ๐Ÿ’ฐ Calculate estimated price
        distance = pickup.distance_to(destination)
        self.estimated_price = Money(
            amount=round(5.0 + (distance * 2.5), 2)  # Base + per km
        )
        
        self._record_event("RideRequested", {
            "pickup": pickup.address,
            "destination": destination.address,
            "estimated_price": self.estimated_price.amount
        })
        
    def assign_driver(self, driver_id: str):
        """๐Ÿš— Assign a driver to the ride"""
        if self.status != RideStatus.REQUESTED:
            raise ValueError(f"Cannot assign driver in {self.status.value} status! ๐Ÿšซ")
            
        self.driver_id = driver_id
        self.status = RideStatus.DRIVER_ASSIGNED
        self._record_event("DriverAssigned", {"driver_id": driver_id})
        print(f"๐ŸŽฏ Driver {driver_id} assigned to ride!")
        
    def start_ride(self):
        """๐Ÿ Start the ride"""
        if self.status != RideStatus.DRIVER_ASSIGNED:
            raise ValueError("No driver assigned yet! ๐Ÿคท")
            
        self.status = RideStatus.IN_PROGRESS
        self._record_event("RideStarted", {"start_time": datetime.now()})
        print(f"๐Ÿš— Ride started! Heading to {self.destination.address}")
        
    def complete_ride(self, actual_distance: float):
        """๐Ÿ Complete the ride"""
        if self.status != RideStatus.IN_PROGRESS:
            raise ValueError("Ride not in progress! ๐Ÿ›‘")
            
        # ๐Ÿ’ต Calculate final price based on actual distance
        self.price = Money(amount=round(5.0 + (actual_distance * 2.5), 2))
        self.status = RideStatus.COMPLETED
        
        self._record_event("RideCompleted", {
            "final_price": self.price.amount,
            "distance": actual_distance
        })
        print(f"โœ… Ride completed! Total: ${self.price.amount}")
        
    def _record_event(self, event_type: str, data: dict):
        """๐Ÿ“ Record domain events"""
        self.events.append({
            "type": event_type,
            "timestamp": datetime.now(),
            "data": data
        })

# ๐ŸŽฎ Test our ride-sharing domain!
pickup = Location(40.7128, -74.0060, "Times Square, NYC ๐Ÿ—ฝ")
destination = Location(40.7614, -73.9776, "Central Park, NYC ๐ŸŒณ")

ride = Ride("customer123", pickup, destination)
print(f"๐Ÿ’ฐ Estimated price: ${ride.estimated_price.amount}")

ride.assign_driver("driver456")
ride.start_ride()
ride.complete_ride(actual_distance=3.2)

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Repository Pattern

When youโ€™re ready to level up, implement the repository pattern:

# ๐ŸŽฏ Repository pattern for data access
from abc import ABC, abstractmethod
from typing import Dict, Optional, List

class Repository(ABC):
    """๐Ÿ“ฆ Abstract repository interface"""
    
    @abstractmethod
    def add(self, entity):
        pass
        
    @abstractmethod
    def get(self, entity_id: str):
        pass
        
    @abstractmethod
    def update(self, entity):
        pass
        
    @abstractmethod
    def delete(self, entity_id: str):
        pass

class InMemoryProductRepository(Repository):
    """๐Ÿ’พ In-memory implementation for testing"""
    
    def __init__(self):
        self.storage: Dict[str, Product] = {}
        
    def add(self, product: Product):
        """โž• Add product to repository"""
        self.storage[product.id] = product
        print(f"๐Ÿ’พ Saved product: {product.name}")
        
    def get(self, product_id: str) -> Optional[Product]:
        """๐Ÿ” Find product by ID"""
        return self.storage.get(product_id)
        
    def update(self, product: Product):
        """๐Ÿ”„ Update existing product"""
        if product.id not in self.storage:
            raise ValueError(f"Product {product.id} not found! ๐Ÿ˜ฑ")
        self.storage[product.id] = product
        
    def delete(self, product_id: str):
        """๐Ÿ—‘๏ธ Remove product"""
        if product_id in self.storage:
            del self.storage[product_id]
            print(f"๐Ÿ—‘๏ธ Deleted product {product_id}")
            
    def find_by_name(self, name: str) -> List[Product]:
        """๐Ÿ”Ž Custom query method"""
        return [p for p in self.storage.values() if name.lower() in p.name.lower()]

๐Ÿ—๏ธ Domain Services

For complex business logic that doesnโ€™t belong to a single entity:

# ๐Ÿš€ Domain service for complex operations
class PricingService:
    """๐Ÿ’ฐ Service for pricing calculations"""
    
    def __init__(self, discount_repository: Repository):
        self.discount_repository = discount_repository
        
    def calculate_total(self, items: List[tuple[Product, int]], 
                       customer_type: str = "regular") -> Money:
        """๐Ÿงฎ Calculate total with discounts"""
        subtotal = Money(0.0)
        
        for product, quantity in items:
            item_total = Money(product.price.amount * quantity)
            subtotal = subtotal + item_total
            
        # ๐ŸŽ Apply customer discount
        discount_rate = self._get_discount_rate(customer_type)
        if discount_rate > 0:
            discount_amount = Money(subtotal.amount * discount_rate)
            total = Money(subtotal.amount - discount_amount.amount)
            print(f"๐ŸŽ‰ Applied {discount_rate*100}% discount!")
            return total
            
        return subtotal
        
    def _get_discount_rate(self, customer_type: str) -> float:
        """๐Ÿท๏ธ Get discount rate by customer type"""
        discounts = {
            "regular": 0.0,
            "premium": 0.1,   # 10% off
            "vip": 0.2        # 20% off
        }
        return discounts.get(customer_type, 0.0)

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Anemic Domain Models

# โŒ Wrong way - no behavior, just data!
class Order:
    def __init__(self):
        self.id = None
        self.items = []
        self.total = 0.0
        self.status = "pending"

# โœ… Correct way - rich domain model with behavior!
class Order:
    def __init__(self, customer_id: str):
        self.id = str(uuid.uuid4())
        self.customer_id = customer_id
        self.items: List[OrderItem] = []
        self.status = OrderStatus.PENDING
        
    def add_item(self, product: Product, quantity: int):
        """๐Ÿ›’ Business logic lives here!"""
        if self.status != OrderStatus.PENDING:
            raise ValueError("Cannot modify confirmed order! ๐Ÿ”’")
        # More business rules...
        
    def calculate_total(self) -> Money:
        """๐Ÿ’ฐ Order knows how to calculate its total"""
        return sum(item.subtotal() for item in self.items)

๐Ÿคฏ Pitfall 2: Mixing Infrastructure with Domain

# โŒ Dangerous - domain depends on infrastructure!
class Product:
    def save_to_database(self):
        import sqlite3  # ๐Ÿ’ฅ Domain shouldn't know about DB!
        conn = sqlite3.connect('products.db')
        # ...

# โœ… Safe - keep infrastructure separate!
class Product:
    """Pure domain model"""
    def update_price(self, new_price: Money):
        if new_price.amount < 0:
            raise ValueError("Price cannot be negative! ๐Ÿ’ธ")
        self.price = new_price
        
# Infrastructure layer handles persistence
class SQLProductRepository(Repository):
    def add(self, product: Product):
        # Database logic here
        pass

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Ubiquitous Language: Use the same terms as your domain experts
  2. ๐Ÿ“ฆ Bounded Contexts: Keep related concepts together, separate others
  3. ๐Ÿ›ก๏ธ Protect Invariants: Aggregates enforce business rules
  4. ๐ŸŽจ Rich Domain Models: Put behavior where the data is
  5. โœจ Event Sourcing: Consider storing events instead of state

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Hotel Booking System

Create a DDD-based hotel booking system:

๐Ÿ“‹ Requirements:

  • โœ… Rooms with different types (single, double, suite)
  • ๐Ÿท๏ธ Dynamic pricing based on season and occupancy
  • ๐Ÿ‘ค Guest profiles with loyalty levels
  • ๐Ÿ“… Booking with check-in/check-out dates
  • ๐ŸŽจ Each room type needs special amenities!

๐Ÿš€ Bonus Points:

  • Add event sourcing for bookings
  • Implement overbooking protection
  • Create a pricing strategy pattern

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
# ๐ŸŽฏ Hotel booking domain model!
from enum import Enum
from datetime import date, timedelta

class RoomType(Enum):
    SINGLE = ("single", "๐Ÿ›๏ธ", 100.0)
    DOUBLE = ("double", "๐Ÿ›๏ธ๐Ÿ›๏ธ", 150.0)
    SUITE = ("suite", "๐Ÿฐ", 300.0)
    
    def __init__(self, value, emoji, base_price):
        self._value_ = value
        self.emoji = emoji
        self.base_price = base_price

class LoyaltyLevel(Enum):
    BRONZE = ("bronze", "๐Ÿฅ‰", 0.05)
    SILVER = ("silver", "๐Ÿฅˆ", 0.10)
    GOLD = ("gold", "๐Ÿฅ‡", 0.15)
    
    def __init__(self, value, emoji, discount):
        self._value_ = value
        self.emoji = emoji
        self.discount = discount

@dataclass(frozen=True)
class DateRange:
    """๐Ÿ“… Value object for date ranges"""
    check_in: date
    check_out: date
    
    def __post_init__(self):
        if self.check_out <= self.check_in:
            raise ValueError("Check-out must be after check-in! ๐Ÿ“†")
            
    @property
    def nights(self) -> int:
        return (self.check_out - self.check_in).days
        
    def overlaps_with(self, other: 'DateRange') -> bool:
        """๐Ÿ” Check if date ranges overlap"""
        return not (self.check_out <= other.check_in or 
                   self.check_in >= other.check_out)

class Room:
    """๐Ÿจ Room entity"""
    def __init__(self, room_number: str, room_type: RoomType):
        self.room_number = room_number
        self.room_type = room_type
        self.bookings: List['Booking'] = []
        self.amenities = self._get_amenities()
        
    def _get_amenities(self) -> List[str]:
        """โœจ Get amenities by room type"""
        amenities_map = {
            RoomType.SINGLE: ["๐Ÿ“บ TV", "โ˜• Coffee maker"],
            RoomType.DOUBLE: ["๐Ÿ“บ TV", "โ˜• Coffee maker", "๐ŸงŠ Mini-bar"],
            RoomType.SUITE: ["๐Ÿ“บ TV", "โ˜• Coffee maker", "๐ŸงŠ Mini-bar", 
                            "๐Ÿ› Jacuzzi", "๐Ÿพ Champagne"]
        }
        return amenities_map[self.room_type]
        
    def is_available(self, date_range: DateRange) -> bool:
        """๐Ÿ” Check if room is available"""
        for booking in self.bookings:
            if booking.status == BookingStatus.CONFIRMED and \
               booking.date_range.overlaps_with(date_range):
                return False
        return True

class BookingStatus(Enum):
    PENDING = "๐Ÿ”ต Pending"
    CONFIRMED = "โœ… Confirmed"
    CANCELLED = "โŒ Cancelled"
    CHECKED_IN = "๐Ÿจ Checked In"
    CHECKED_OUT = "๐Ÿ‘‹ Checked Out"

class Booking:
    """๐Ÿ“‹ Booking aggregate"""
    def __init__(self, guest_id: str, room: Room, date_range: DateRange):
        if not room.is_available(date_range):
            raise ValueError(f"Room {room.room_number} not available! ๐Ÿ˜ข")
            
        self.id = str(uuid.uuid4())
        self.guest_id = guest_id
        self.room = room
        self.date_range = date_range
        self.status = BookingStatus.PENDING
        self.total_price: Optional[Money] = None
        self.events: List[dict] = []
        
        self._record_event("BookingCreated", {
            "room": room.room_number,
            "dates": f"{date_range.check_in} to {date_range.check_out}"
        })
        
    def confirm(self, pricing_service: 'PricingService', guest: 'Guest'):
        """โœ… Confirm the booking"""
        if self.status != BookingStatus.PENDING:
            raise ValueError("Can only confirm pending bookings! ๐Ÿšซ")
            
        self.total_price = pricing_service.calculate_booking_price(
            self.room, self.date_range, guest.loyalty_level
        )
        self.status = BookingStatus.CONFIRMED
        self.room.bookings.append(self)
        
        self._record_event("BookingConfirmed", {
            "price": self.total_price.amount,
            "loyalty_discount": guest.loyalty_level.discount
        })
        
        print(f"โœ… Booking confirmed! {self.room.room_type.emoji}")
        print(f"๐Ÿ’ฐ Total: ${self.total_price.amount} for {self.date_range.nights} nights")
        
    def _record_event(self, event_type: str, data: dict):
        self.events.append({
            "type": event_type,
            "timestamp": datetime.now(),
            "data": data
        })

class Guest:
    """๐Ÿ‘ค Guest entity"""
    def __init__(self, name: str, email: str):
        self.id = str(uuid.uuid4())
        self.name = name
        self.email = email
        self.loyalty_level = LoyaltyLevel.BRONZE
        self.total_nights = 0
        
    def update_loyalty_status(self):
        """๐Ÿ† Update loyalty based on nights stayed"""
        if self.total_nights >= 50:
            self.loyalty_level = LoyaltyLevel.GOLD
            print(f"๐ŸŽ‰ Congratulations! You're now {self.loyalty_level.emoji} level!")
        elif self.total_nights >= 20:
            self.loyalty_level = LoyaltyLevel.SILVER
            print(f"๐ŸŽŠ Great! You've reached {self.loyalty_level.emoji} level!")

class HotelPricingService:
    """๐Ÿ’ฐ Domain service for pricing"""
    def calculate_booking_price(self, room: Room, date_range: DateRange, 
                               loyalty_level: LoyaltyLevel) -> Money:
        """๐Ÿงฎ Calculate total price with all factors"""
        base_price = room.room_type.base_price
        nights = date_range.nights
        
        # ๐Ÿ“ˆ Seasonal pricing (summer is peak)
        if date_range.check_in.month in [6, 7, 8]:
            base_price *= 1.5  # 50% increase
            print("โ˜€๏ธ Peak season pricing applied!")
            
        subtotal = base_price * nights
        
        # ๐ŸŽ Apply loyalty discount
        discount = subtotal * loyalty_level.discount
        total = subtotal - discount
        
        if discount > 0:
            print(f"{loyalty_level.emoji} Loyalty discount: ${discount:.2f}")
            
        return Money(round(total, 2))

# ๐ŸŽฎ Test our hotel system!
hotel_room = Room("101", RoomType.SUITE)
guest = Guest("Alice Johnson", "[email protected]")
guest.total_nights = 25  # Silver member!
guest.update_loyalty_status()

dates = DateRange(
    date.today() + timedelta(days=30),
    date.today() + timedelta(days=33)
)

booking = Booking(guest.id, hotel_room, dates)
pricing_service = HotelPricingService()
booking.confirm(pricing_service, guest)

print(f"\n๐Ÿจ Room amenities: {', '.join(hotel_room.amenities)}")

๐ŸŽ“ Key Takeaways

Youโ€™ve learned so much! Hereโ€™s what you can now do:

  • โœ… Create rich domain models with confidence ๐Ÿ’ช
  • โœ… Implement DDD building blocks (entities, value objects, aggregates) ๐Ÿ›ก๏ธ
  • โœ… Apply ubiquitous language in real projects ๐ŸŽฏ
  • โœ… Design bounded contexts like a pro ๐Ÿ›
  • โœ… Build event-driven systems with Python! ๐Ÿš€

Remember: DDD is about modeling your software to match your business domain. Itโ€™s here to help you write better, more maintainable code! ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered Domain-Driven Design in Python!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the hotel booking exercise above
  2. ๐Ÿ—๏ธ Apply DDD to your current projectโ€™s most complex domain
  3. ๐Ÿ“š Move on to our next tutorial: Event Sourcing and CQRS
  4. ๐ŸŒŸ Share your DDD journey with others!

Remember: Every DDD expert was once a beginner. Keep modeling, keep learning, and most importantly, have fun building systems that truly reflect your business! ๐Ÿš€


Happy coding! ๐ŸŽ‰๐Ÿš€โœจ