

แนะนำ Python Click CLI และ Domain Driven Design (DDD)
ในโลกของการพัฒนาแอปพลิเคชันสมัยใหม่ การสร้างเครื่องมือ Command Line Interface (CLI) ที่มีประสิทธิภาพและยืดหยุ่นเป็นสิ่งสำคัญอย่างยิ่ง โดยเฉพาะอย่างยิ่งเมื่อเราต้องการนำแนวคิด Domain Driven Design (DDD) มาประยุกต์ใช้เพื่อจัดการกับความซับซ้อนของโดเมนธุรกิจ บทความนี้จะพาคุณสำรวจการผสาน Python Click ซึ่งเป็นไลบรารีสร้าง CLI ที่ทรงพลัง เข้ากับหลักการของ DDD เพื่อสร้างระบบที่ทั้งใช้งานง่ายและบำรุงรักษาได้ดีในระยะยาว
Python Click เป็นไลบรารีที่ได้รับความนิยมอย่างสูงในการพัฒนา CLI ด้วย Python เนื่องจากมี syntax ที่สะอาด ใช้งานง่าย และรองรับฟีเจอร์ที่ซับซ้อน เช่น การจัดการคำสั่งแบบ nested, การ validate input อัตโนมัติ, และการแสดง help text ที่สวยงาม ในขณะที่ DDD เป็นแนวคิดการออกแบบซอฟต์แวร์ที่เน้นการจำลองโมเดลทางธุรกิจลงในโค้ด โดยแยกส่วนที่เป็นโดเมนหลัก (core domain) ออกจากส่วนที่เป็น infrastructure และ application service
เมื่อนำทั้งสองอย่างมารวมกัน เราจะได้ CLI ที่ไม่เพียงแต่ทำงานได้ดี แต่ยังสะท้อนโครงสร้างธุรกิจอย่างชัดเจน ทำให้ทีมพัฒนาสามารถเข้าใจและปรับเปลี่ยนระบบได้ง่ายขึ้น บทความนี้จะอธิบายตั้งแต่พื้นฐานไปจนถึงเทคนิคขั้นสูง พร้อมตัวอย่างการใช้งานจริง
1. พื้นฐานของ Python Click สำหรับ CLI
1.1 การติดตั้งและเริ่มต้นใช้งาน
ก่อนอื่นเราต้องติดตั้ง Python Click ผ่าน pip:
pip install click
จากนั้นเราสามารถสร้าง CLI ง่ายๆ ได้ดังนี้:
import click
@click.command()
@click.option('--name', '-n', default='World', help='ชื่อที่ต้องการทักทาย')
@click.option('--count', '-c', default=1, type=int, help='จำนวนครั้งที่ทักทาย')
def hello(name, count):
"""โปรแกรมทักทายแบบง่าย"""
for i in range(count):
click.echo(f'สวัสดี {name}! ครั้งที่ {i+1}')
if __name__ == '__main__':
hello()
เมื่อรันโปรแกรมด้วยคำสั่ง python hello.py --name SiamCafe --count 3 จะได้ผลลัพธ์:
สวัสดี SiamCafe! ครั้งที่ 1
สวัสดี SiamCafe! ครั้งที่ 2
สวัสดี SiamCafe! ครั้งที่ 3
1.2 ฟีเจอร์สำคัญของ Click
Click มีฟีเจอร์เด่นหลายอย่างที่ทำให้การพัฒนา CLI สะดวกขึ้น:
- Argument vs Option: Argument เป็นค่าบังคับที่ต้องระบุตามตำแหน่ง ส่วน Option เป็นค่าที่ระบุผ่าน flag เช่น
--name - Type Validation: รองรับ type ต่างๆ เช่น int, float, Path, Choice, และ custom type
- Nested Commands: สามารถสร้างกลุ่มคำสั่ง (Group) เพื่อจัดระเบียบคำสั่งย่อย
- Callbacks และ Validation: สามารถเพิ่ม logic ก่อนหรือหลังการรับค่า
- Help Text อัตโนมัติ: Click จะสร้าง help text จาก docstring และคำอธิบาย option
2. หลักการ Domain Driven Design (DDD) สำหรับ CLI
2.1 แนวคิดพื้นฐานของ DDD
Domain Driven Design (DDD) เป็นแนวคิดที่ Eric Evans นำเสนอในหนังสือ “Domain-Driven Design: Tackling Complexity in the Heart of Software” โดยเน้นการทำความเข้าใจและจำลองโมเดลธุรกิจอย่างลึกซึ้ง องค์ประกอบสำคัญของ DDD ได้แก่:
- Entity: ออบเจกต์ที่มี identity ไม่ซ้ำกัน เช่น User, Order
- Value Object: ออบเจกต์ที่ไม่มี identity ใช้แทนค่าต่างๆ เช่น Address, Money
- Aggregate: กลุ่มของ Entity และ Value Object ที่ทำงานร่วมกัน โดยมี Aggregate Root เป็นตัวควบคุม
- Domain Service: Service ที่มี logic ทางธุรกิจที่ไม่เหมาะจะอยู่ใน Entity หรือ Value Object
- Repository: ตัวกลางสำหรับเข้าถึงข้อมูลจากแหล่งเก็บข้อมูล
- Application Service: Service ที่ประสานงานระหว่าง domain layer และ infrastructure
2.2 การประยุกต์ DDD กับ CLI
เมื่อเราสร้าง CLI ด้วย DDD เราจะแยกชั้นต่างๆ ดังนี้:
| ชั้น (Layer) | บทบาท | ตัวอย่าง |
|---|---|---|
| Interface (CLI) | รับคำสั่งจากผู้ใช้ ส่งต่อไปยัง Application Service | Click commands, options, arguments |
| Application | จัดการ workflow, transaction, authorization | Application Service, DTO |
| Domain | Business logic, rules, entities | Entity, Value Object, Domain Service |
| Infrastructure | การเข้าถึงข้อมูล, external services, persistence | Repository implementation, database |
3. การออกแบบ CLI ด้วย DDD — กรณีศึกษา ระบบจัดการคำสั่งซื้อ
3.1 การวิเคราะห์โดเมนธุรกิจ
สมมติว่าเราต้องสร้าง CLI สำหรับระบบจัดการคำสั่งซื้อของร้านค้าออนไลน์ โดยมีฟังก์ชันหลักดังนี้:
- สร้างคำสั่งซื้อใหม่ (Create Order)
- ดูรายละเอียดคำสั่งซื้อ (View Order)
- อัปเดตสถานะคำสั่งซื้อ (Update Order Status)
- ยกเลิกคำสั่งซื้อ (Cancel Order)
- รายงานยอดขาย (Sales Report)
จากโดเมนนี้ เราสามารถระบุ Entity หลักได้แก่ Order และ Customer ส่วน Value Object เช่น OrderItem, Money, Address
3.2 การสร้างโครงสร้างโปรเจกต์ตาม DDD
โครงสร้างโปรเจกต์ที่แนะนำ:
order_cli/
├── cli/
│ ├── __init__.py
│ ├── main.py # จุดเริ่มต้น CLI
│ ├── commands/
│ │ ├── __init__.py
│ │ ├── order_commands.py # คำสั่งเกี่ยวกับ Order
│ │ └── report_commands.py # คำสั่งเกี่ยวกับรายงาน
│ └── config.py # ตั้งค่า CLI
├── domain/
│ ├── __init__.py
│ ├── models/
│ │ ├── __init__.py
│ │ ├── order.py # Order entity
│ │ ├── customer.py # Customer entity
│ │ └── value_objects.py # Money, Address ฯลฯ
│ ├── services/
│ │ ├── __init__.py
│ │ ├── order_service.py # Domain service
│ │ └── pricing_service.py # ราคาและส่วนลด
│ └── repositories/
│ ├── __init__.py
│ └── interfaces.py # Abstract repository interface
├── application/
│ ├── __init__.py
│ └── services/
│ ├── __init__.py
│ ├── order_app_service.py # Application service
│ └── report_app_service.py
├── infrastructure/
│ ├── __init__.py
│ ├── repositories/
│ │ ├── __init__.py
│ │ ├── in_memory_order_repo.py
│ │ └── postgres_order_repo.py
│ └── external/
│ └── payment_gateway.py
└── tests/
├── __init__.py
├── test_domain/
├── test_application/
└── test_cli/
3.3 การเขียน Domain Layer
เริ่มจากโมเดลในโดเมน:
# domain/models/order.py
from dataclasses import dataclass
from datetime import datetime
from typing import List, Optional
from .value_objects import Money, OrderItem, OrderStatus, Address
@dataclass
class Order:
id: str
customer_id: str
items: List[OrderItem]
shipping_address: Address
total_amount: Money
status: OrderStatus
created_at: datetime
updated_at: Optional[datetime] = None
def can_cancel(self) -> bool:
"""ตรวจสอบว่าสามารถยกเลิกคำสั่งซื้อได้หรือไม่"""
return self.status in [OrderStatus.PENDING, OrderStatus.CONFIRMED]
def cancel(self) -> None:
"""ยกเลิกคำสั่งซื้อ (มี business logic)"""
if not self.can_cancel():
raise ValueError("ไม่สามารถยกเลิกคำสั่งซื้อในสถานะนี้ได้")
self.status = OrderStatus.CANCELLED
self.updated_at = datetime.now()
# domain/models/value_objects.py
from dataclasses import dataclass
from enum import Enum
from typing import List
class OrderStatus(Enum):
PENDING = "pending"
CONFIRMED = "confirmed"
SHIPPED = "shipped"
DELIVERED = "delivered"
CANCELLED = "cancelled"
@dataclass(frozen=True)
class Money:
amount: float
currency: str = "THB"
def __add__(self, other: 'Money') -> 'Money':
if self.currency != other.currency:
raise ValueError("สกุลเงินไม่ตรงกัน")
return Money(self.amount + other.amount, self.currency)
@dataclass(frozen=True)
class OrderItem:
product_id: str
product_name: str
quantity: int
unit_price: Money
def total_price(self) -> Money:
return Money(self.unit_price.amount * self.quantity, self.unit_price.currency)
@dataclass(frozen=True)
class Address:
street: str
city: str
province: str
zip_code: str
country: str = "ประเทศไทย"
4. การสร้าง CLI ด้วย Click และเชื่อมต่อกับ Application Layer
4.1 การออกแบบคำสั่ง CLI
เราจะออกแบบคำสั่ง CLI ดังนี้:
order create --customer-id C001 --items "PROD1:2,PROD2:1" --address "123 ถ.สุขุมวิท,กรุงเทพฯ,10110"order view ORDER-001order update-status ORDER-001 --status shippedorder cancel ORDER-001report sales --from 2026-01-01 --to 2026-01-31
4.2 การเขียน CLI ด้วย Click
# cli/main.py
import click
from cli.commands.order_commands import order_group
from cli.commands.report_commands import report_group
@click.group()
@click.version_option(version="1.0.0")
def cli():
"""ระบบจัดการคำสั่งซื้อ - Order Management CLI"""
pass
cli.add_command(order_group)
cli.add_command(report_group)
if __name__ == '__main__':
cli()
# cli/commands/order_commands.py
import click
from application.services.order_app_service import OrderApplicationService
from infrastructure.repositories.in_memory_order_repo import InMemoryOrderRepository
@click.group()
def order_group():
"""จัดการคำสั่งซื้อ"""
pass
@order_group.command()
@click.option('--customer-id', required=True, help='รหัสลูกค้า')
@click.option('--items', required=True, help='รายการสินค้า รูปแบบ: PROD1:2,PROD2:1')
@click.option('--address', required=True, help='ที่อยู่จัดส่ง')
def create(customer_id, items, address):
"""สร้างคำสั่งซื้อใหม่"""
app_service = OrderApplicationService(InMemoryOrderRepository())
# แปลง items string เป็น list
item_list = []
for item_str in items.split(','):
prod_id, qty = item_str.split(':')
item_list.append({'product_id': prod_id, 'quantity': int(qty)})
try:
order_id = app_service.create_order(
customer_id=customer_id,
items=item_list,
address=address
)
click.echo(f'✅ สร้างคำสั่งซื้อสำเร็จ: {order_id}')
except ValueError as e:
click.echo(f'❌ เกิดข้อผิดพลาด: {e}', err=True)
@order_group.command()
@click.argument('order_id')
def view(order_id):
"""ดูรายละเอียดคำสั่งซื้อ"""
app_service = OrderApplicationService(InMemoryOrderRepository())
order = app_service.get_order(order_id)
if not order:
click.echo(f'❌ ไม่พบคำสั่งซื้อ: {order_id}')
return
click.echo(f'📋 คำสั่งซื้อ: {order.id}')
click.echo(f' ลูกค้า: {order.customer_id}')
click.echo(f' สถานะ: {order.status.value}')
click.echo(f' จำนวนเงิน: {order.total_amount.amount} {order.total_amount.currency}')
click.echo(f' สร้างเมื่อ: {order.created_at}')
click.echo(' รายการสินค้า:')
for item in order.items:
click.echo(f' - {item.product_name} x{item.quantity} = {item.total_price().amount}')
@order_group.command()
@click.argument('order_id')
@click.option('--status', type=click.Choice(['confirmed', 'shipped', 'delivered', 'cancelled']),
required=True, help='สถานะใหม่')
def update_status(order_id, status):
"""อัปเดตสถานะคำสั่งซื้อ"""
app_service = OrderApplicationService(InMemoryOrderRepository())
try:
app_service.update_order_status(order_id, status)
click.echo(f'✅ อัปเดตสถานะคำสั่งซื้อ {order_id} เป็น {status} สำเร็จ')
except ValueError as e:
click.echo(f'❌ เกิดข้อผิดพลาด: {e}', err=True)
@order_group.command()
@click.argument('order_id')
def cancel(order_id):
"""ยกเลิกคำสั่งซื้อ"""
app_service = OrderApplicationService(InMemoryOrderRepository())
try:
app_service.cancel_order(order_id)
click.echo(f'✅ ยกเลิกคำสั่งซื้อ {order_id} สำเร็จ')
except ValueError as e:
click.echo(f'❌ เกิดข้อผิดพลาด: {e}', err=True)
4.3 การเขียน Application Service
# application/services/order_app_service.py
from typing import List, Dict, Any, Optional
from domain.models.order import Order, OrderItem, OrderStatus, Address, Money
from domain.repositories.interfaces import OrderRepository
from domain.services.pricing_service import PricingService
from datetime import datetime
import uuid
class OrderApplicationService:
def __init__(self, repository: OrderRepository):
self.repository = repository
self.pricing_service = PricingService()
def create_order(self, customer_id: str, items: List[Dict[str, Any]],
address: str) -> str:
"""สร้างคำสั่งซื้อใหม่ (Application Service workflow)"""
# 1. แปลงข้อมูลจาก CLI เป็น Domain objects
order_items = []
for item in items:
# ในระบบจริงควร query product จาก repository
product = self._get_product(item['product_id'])
order_items.append(
OrderItem(
product_id=product['id'],
product_name=product['name'],
quantity=item['quantity'],
unit_price=Money(product['price'])
)
)
# 2. แปลงที่อยู่
addr_parts = address.split(',')
shipping_address = Address(
street=addr_parts[0].strip(),
city=addr_parts[1].strip() if len(addr_parts) > 1 else '',
province=addr_parts[2].strip() if len(addr_parts) > 2 else '',
zip_code=addr_parts[3].strip() if len(addr_parts) > 3 else ''
)
# 3. คำนวณราคา (ใช้ Domain Service)
total_amount = self.pricing_service.calculate_total(order_items)
# 4. สร้าง Order Entity
order = Order(
id=f"ORDER-{uuid.uuid4().hex[:8].upper()}",
customer_id=customer_id,
items=order_items,
shipping_address=shipping_address,
total_amount=total_amount,
status=OrderStatus.PENDING,
created_at=datetime.now()
)
# 5. บันทึกผ่าน Repository
self.repository.save(order)
return order.id
def get_order(self, order_id: str) -> Optional[Order]:
"""ค้นหาคำสั่งซื้อ"""
return self.repository.find_by_id(order_id)
def update_order_status(self, order_id: str, new_status: str) -> None:
"""อัปเดตสถานะคำสั่งซื้อ"""
order = self.repository.find_by_id(order_id)
if not order:
raise ValueError(f"ไม่พบคำสั่งซื้อ: {order_id}")
# ใช้ Domain logic ในการเปลี่ยนสถานะ
status_map = {
'confirmed': OrderStatus.CONFIRMED,
'shipped': OrderStatus.SHIPPED,
'delivered': OrderStatus.DELIVERED,
'cancelled': OrderStatus.CANCELLED
}
new_status_enum = status_map[new_status]
# ในระบบจริงควรมี state machine pattern
order.status = new_status_enum
order.updated_at = datetime.now()
self.repository.save(order)
def cancel_order(self, order_id: str) -> None:
"""ยกเลิกคำสั่งซื้อ (ใช้ Domain logic จาก Entity)"""
order = self.repository.find_by_id(order_id)
if not order:
raise ValueError(f"ไม่พบคำสั่งซื้อ: {order_id}")
order.cancel() # ใช้ domain logic
self.repository.save(order)
def _get_product(self, product_id: str) -> Dict:
"""จำลองการดึงข้อมูลสินค้า"""
products = {
'PROD1': {'id': 'PROD1', 'name': 'สินค้า A', 'price': 100.0},
'PROD2': {'id': 'PROD2', 'name': 'สินค้า B', 'price': 250.0},
'PROD3': {'id': 'PROD3', 'name': 'สินค้า C', 'price': 500.0},
}
return products.get(product_id, {'id': product_id, 'name': 'สินค้าทั่วไป', 'price': 0.0})
5. การเปรียบเทียบ: CLI แบบดั้งเดิม vs CLI แบบ DDD
| คุณสมบัติ | CLI แบบดั้งเดิม | CLI แบบ DDD |
|---|---|---|
| การจัดโครงสร้าง | ทุกอย่างรวมอยู่ในไฟล์เดียวหรือไม่กี่ไฟล์ | แยกเป็น layer อย่างชัดเจน (CLI, Application, Domain, Infrastructure) |
| Business Logic | ปนอยู่กับโค้ด CLI ทำให้ทดสอบยาก | แยกออกจากกัน สามารถทดสอบ Domain logic ได้โดยไม่ต้องพึ่ง CLI |
| ความสามารถในการขยาย | เมื่อเพิ่มฟีเจอร์ โค้ดจะซับซ้อนและยากต่อการบำรุงรักษา | สามารถเพิ่มฟีเจอร์ใหม่โดยไม่กระทบ layer อื่น |
| การเปลี่ยน Infrastructure | ต้องแก้ไขโค้ดหลายจุด | เปลี่ยนแค่ Repository implementation โดยใช้ Dependency Injection |
| การทดสอบ | ต้อง mock ทั้ง CLI และ logic | ทดสอบ Domain และ Application Service แยกจาก CLI ได้ |
| การทำงานเป็นทีม | ยากเพราะทุกคนต้องเข้าใจโค้ดทั้งหมด | แยกความรับผิดชอบชัดเจน แต่ละคนรับผิดชอบ layer ของตัวเอง |
6. Best Practices และเทคนิคขั้นสูง
6.1 การใช้ Dependency Injection
การใช้ DI ช่วยให้เราสามารถเปลี่ยน implementation ได้ง่าย เช่น การเปลี่ยนจาก In-Memory Repository ไปเป็น PostgreSQL Repository โดยไม่ต้องแก้ไข Application Service:
# cli/config.py
from infrastructure.repositories.in_memory_order_repo import InMemoryOrderRepository
from infrastructure.repositories.postgres_order_repo import PostgresOrderRepository
class ServiceContainer:
"""Container สำหรับจัดการ dependencies"""
@staticmethod
def get_order_repository():
# สามารถเปลี่ยนเป็น PostgresOrderRepository() ได้ทุกเมื่อ
return InMemoryOrderRepository()
@staticmethod
def get_order_app_service():
repo = ServiceContainer.get_order_repository()
from application.services.order_app_service import OrderApplicationService
return OrderApplicationService(repo)
6.2 การจัดการข้อผิดพลาดและการแสดงผล
ควรสร้าง error handling ที่เป็นระบบ:
- ใช้ custom exception classes สำหรับ domain errors
- สร้าง decorator สำหรับจับ exception และแสดงผลเป็นภาษาไทย
- ใช้
click.style()เพื่อเพิ่มสีสันให้ผลลัพธ์
6.3 การทดสอบ (Testing)
การทดสอบควรครอบคลุมทุกระดับ:
- Unit Test: ทดสอบ Domain logic (Entity, Value Object, Domain Service)
- Integration Test: ทดสอบ Application Service ร่วมกับ Repository
- CLI Test: ทดสอบคำสั่ง CLI โดยใช้
click.testing.CliRunner
# tests/test_cli/test_order_commands.py
from click.testing import CliRunner
from cli.main import cli
def test_create_order_command():
runner = CliRunner()
result = runner.invoke(cli, [
'order', 'create',
'--customer-id', 'C001',
'--items', 'PROD1:2,PROD2:1',
'--address', '123 ถ.สุขุมวิท,กรุงเทพฯ,10110'
])
assert result.exit_code == 0
assert 'สร้างคำสั่งซื้อสำเร็จ' in result.output
7. กรณีการใช้งานจริง (Real-World Use Cases)
7.1 ระบบจัดการคลังสินค้า (Warehouse Management)
ในระบบคลังสินค้าขนาดใหญ่ CLI ที่ใช้ DDD ช่วยให้:
- พนักงานสามารถสแกนบาร์โค้ดผ่าน CLI เพื่อรับ-ส่งสินค้า
- ผู้จัดการสามารถดูรายงานสินค้าคงคลังแบบ real-time
- ระบบสามารถตรวจสอบความถูกต้องของข้อมูลก่อนบันทึกลงฐานข้อมูล
7.2 ระบบ DevOps และ Infrastructure Management
ทีม DevOps สามารถใช้ CLI แบบ DDD เพื่อ:
- จัดการ deployment pipeline
- ตรวจสอบสถานะ server และ service
- ดำเนินการ rollback เมื่อเกิดปัญหา
7.3 ระบบธนาคารและการเงิน
สำหรับระบบการเงินที่ต้องการความถูกต้องสูง:
- การทำธุรกรรมต้องมี business logic ที่ซับซ้อน (เช่น การตรวจสอบวงเงิน, การคำนวณดอกเบี้ย)
- DDD ช่วยให้ logic เหล่านี้แยกออกจากกันอย่างชัดเจนและทดสอบได้
- CLI ใช้สำหรับพนักงานธนาคารในการดำเนินการพิเศษ
Summary
การผสาน Python Click CLI เข้ากับ Domain Driven Design (DDD) เป็นแนวทางที่มีประสิทธิภาพสูงสำหรับการพัฒนาเครื่องมือ command line ที่ซับซ้อนและต้องการความยืดหยุ่นในการบำรุงรักษาระยะยาว โดยสรุปข้อดีสำคัญได้ดังนี้:
- การแยกความรับผิดชอบ (Separation of Concerns): CLI layer จัดการเฉพาะการรับ-ส่งข้อมูลกับผู้ใช้ ส่วน Domain logic ถูกแยกออกไปอย่างชัดเจน ทำให้โค้ดอ่านง่ายและทดสอบได้ดีขึ้น
- ความยืดหยุ่นในการเปลี่ยน Infrastructure: ด้วย Dependency Injection และ Repository pattern เราสามารถเปลี่ยนจาก In-Memory ไปเป็นฐานข้อมูลจริงได้โดยไม่ต้องแก้ไข business logic
- การทำงานเป็นทีมที่มีประสิทธิภาพ: นักพัฒนาสามารถทำงานแยกส่วนกันได้ เช่น คนหนึ่งพัฒนา CLI interface อีกคนพัฒนา Domain logic โดยไม่ต้องรอกัน
- การทดสอบที่ครอบคลุม: เราสามารถทดสอบ Domain logic โดยไม่ต้องพึ่ง CLI และทดสอบ CLI โดยไม่ต้องพึ่งระบบจริง
- ความพร้อมสำหรับการขยายระบบ: เมื่อระบบเติบโตขึ้น การเพิ่มฟีเจอร์ใหม่ทำได้ง่ายโดยไม่กระทบส่วนอื่น
สำหรับผู้ที่สนใจเริ่มต้นใช้งาน แนะนำให้เริ่มจากโปรเจกต์เล็กๆ ก่อน เช่น ระบบจัดการรายการสิ่งที่ต้องทำ (Todo List) หรือระบบจัดการคลังสินค้าขนาดเล็ก แล้วค่อยๆ ขยายไปสู่ระบบที่ซับซ้อนมากขึ้น การลงทุนเวลาในการออกแบบโครงสร้างที่ดีตั้งแต่ต้นจะช่วยประหยัดเวลาและลดปัญหาการบำรุงรักษาในระยะยาวอย่างมาก
ท้ายที่สุดนี้ การเลือกใช้ DDD ไม่จำเป็นต้องทำทุกอย่างให้สมบูรณ์แบบในครั้งเดียว สามารถเริ่มจาก core domain ที่สำคัญที่สุดก่อน แล้วค่อยๆ ปรับปรุงส่วนอื่นๆ เมื่อมีความจำเป็น สิ่งสำคัญคือการทำความเข้าใจโดเมนธุรกิจอย่างลึกซึ้ง และสร้างโมเดลที่สะท้อนความเข้าใจนั้นออกมาในโค้ดอย่างถูกต้อง