Separation Of Concerns
Definition

Classic Example: MVC

Layered Architecture

Code Example
// BAD: All concerns mixed together
public class OrderController {
public String placeOrder(HttpRequest request) {
// Concern 1: Input parsing
String json = request.getBody();
JSONObject data = new JSONObject(json);
String productId = data.getString("productId");
int quantity = data.getInt("quantity");
// Concern 2: Validation
if (quantity <= 0) {
return "{\"error\": \"Invalid quantity\"}";
}
// Concern 3: Business logic
double price = 0;
try (Connection conn = DriverManager.getConnection(URL)) {
// Concern 4: Data access mixed in
PreparedStatement ps = conn.prepareStatement(
"SELECT price FROM products WHERE id = ?");
ps.setString(1, productId);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
price = rs.getDouble("price");
}
double total = price * quantity;
if (total > 1000) {
total = total * 0.9; // 10% discount
}
// Concern 5: More data access
PreparedStatement insert = conn.prepareStatement(
"INSERT INTO orders (product_id, quantity, total) VALUES (?, ?, ?)");
insert.setString(1, productId);
insert.setInt(2, quantity);
insert.setDouble(3, total);
insert.executeUpdate();
// Concern 6: Response formatting
return "{\"orderId\": 123, \"total\": " + total + "}";
} catch (SQLException e) {
// Concern 7: Error handling
return "{\"error\": \"Database error\"}";
}
}
}
Properly Separated
// GOOD: Concerns separated into distinct classes
// Controller: Handles HTTP concerns only
@RestController
public class OrderController {
private final OrderService orderService;
@PostMapping("/orders")
public ResponseEntity<OrderResponse> placeOrder(
@Valid @RequestBody OrderRequest request) {
Order order = orderService.placeOrder(
request.getProductId(),
request.getQuantity()
);
return ResponseEntity.ok(OrderResponse.from(order));
}
}
// Service: Business logic concern
@Service
public class OrderService {
private final ProductRepository productRepo;
private final OrderRepository orderRepo;
private final PricingService pricingService;
@Transactional
public Order placeOrder(String productId, int quantity) {
Product product = productRepo.findById(productId)
.orElseThrow(() -> new ProductNotFoundException(productId));
Money total = pricingService.calculateTotal(product, quantity);
Order order = new Order(product, quantity, total);
return orderRepo.save(order);
}
}
// Domain: Business rules concern
@Service
public class PricingService {
private static final Money DISCOUNT_THRESHOLD = Money.of(1000);
private static final BigDecimal DISCOUNT_RATE = new BigDecimal("0.10");
public Money calculateTotal(Product product, int quantity) {
Money subtotal = product.getPrice().multiply(quantity);
if (subtotal.isGreaterThan(DISCOUNT_THRESHOLD)) {
return subtotal.subtract(subtotal.multiply(DISCOUNT_RATE));
}
return subtotal;
}
}
// Repository: Data access concern
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
}
// DTO: Presentation concern
public record OrderRequest(
@NotBlank String productId,
@Positive int quantity
) {}
public record OrderResponse(Long id, BigDecimal total) {
public static OrderResponse from(Order order) {
return new OrderResponse(order.getId(), order.getTotal().getAmount());
}
}
Cross-Cutting Concerns

SoC in Different Contexts

Tips & Tricks
