Keep Controllers Thin
Controllers should only handle HTTP concerns: parsing requests, validating input format, calling use cases, and formatting responses. No business logic.
Incorrect (fat controller with business logic):
class OrderController:
@app.route('/orders', methods=['POST'])
def create_order(self):
data = request.json
# Input validation - OK in controller
if not data.get('items'):
return jsonify({'error': 'Items required'}), 400
# Business logic - WRONG in controller
customer = db.query(Customer).get(data['customer_id'])
if not customer.is_active:
return jsonify({'error': 'Inactive customer'}), 400
total = 0
for item in data['items']:
product = db.query(Product).get(item['product_id'])
if product.stock < item['quantity']:
return jsonify({'error': f'{product.name} out of stock'}), 400
total += product.price * item['quantity']
# More business logic
if total > customer.credit_limit:
return jsonify({'error': 'Exceeds credit limit'}), 400
order = Order(
customer_id=customer.id,
items=data['items'],
total=total
)
db.session.add(order)
db.session.commit()
return jsonify({'order_id': order.id}), 201
Correct (thin controller delegates to use case):
class OrderController:
def __init__(self, create_order_use_case: CreateOrderUseCase):
self.create_order = create_order_use_case
@app.route('/orders', methods=['POST'])
def create(self):
# Parse HTTP request
data = request.json
# Validate request format (not business rules)
if not data.get('items'):
return jsonify({'error': 'Items required'}), 400
# Build command
command = CreateOrderCommand(
customer_id=data['customer_id'],
items=[
OrderItemCommand(p['product_id'], p['quantity'])
for p in data['items']
]
)
# Delegate to use case
try:
result = self.create_order.execute(command)
return jsonify({'order_id': result.order_id}), 201
except CustomerInactiveError:
return jsonify({'error': 'Customer account inactive'}), 400
except InsufficientStockError as e:
return jsonify({'error': f'{e.product_name} out of stock'}), 400
except CreditLimitExceededError:
return jsonify({'error': 'Order exceeds credit limit'}), 400
Controller responsibilities:
- Parse HTTP request to command/query objects
- Validate request format (required fields present)
- Call appropriate use case
- Map exceptions to HTTP status codes
- Format response
Use case responsibilities:
- Business validation (credit limits, stock)
- Business logic (calculations, state changes)
- Orchestrate entities and repositories
Reference: Clean Architecture - Controllers