Skip to main content

Separation of Concerns

Design systems where different areas address different aspects of functionality, improving modularity and maintainability.

TL;DR

Separate different concerns into distinct modules so each handles one aspect of functionality. A web application might separate HTTP handling from business logic from data persistence. Benefits include easier testing, independent development, and flexible architectural changes. Related concepts: Single Responsibility Principle, layering, modularity.

Learning Objectives

You will be able to:

  • Identify concerns in a system and separate them
  • Design module boundaries that align with concerns
  • Understand layering architectures and their tradeoffs
  • Recognize when concerns are mixed inappropriately
  • Apply separation strategically without over-engineering

Motivating Scenario

A payment service mixes concerns throughout its code: HTTP handlers call database queries directly, business logic performs logging, data access handles validation, and UI code contains calculations. When requirements change—adding a new API format, switching databases, changing validation rules—you must update multiple modules. A single change ripples everywhere.

With clear separation: HTTP handlers delegate to business logic. Business logic calls data access. Each layer has one job. A database change affects only the data layer. New API format affects only the handler layer. Changes are localized and testable.

Core Concepts

What Is a Concern?

A concern is an aspect of functionality that can be meaningfully separated. Examples:

  • HTTP handling: parsing requests, returning responses
  • Business logic: calculations, rules, workflows
  • Data persistence: reading/writing to databases
  • Authentication: verifying user identity
  • Logging: recording events
  • Validation: checking data correctness
Concerns in Layered Architecture

Cross-Cutting Concerns

Some concerns span multiple layers: logging, security, error handling, performance monitoring. These need special handling—frameworks like aspect-oriented programming or middleware address them.

Cohesion

Within a module, keep code related to the same concern together. High cohesion means code in a module works together toward one goal.

Practical Example

# ❌ MIXED CONCERNS - Everything tangled together
class UserController:
def create_user(self, request):
# HTTP parsing (concerns: request handling)
name = request.json.get('name')
email = request.json.get('email')

# Validation (concern: validation)
if not email or '@' not in email:
return {'error': 'Invalid email'}, 400

# Business logic (concern: user creation)
user_id = str(uuid.uuid4())

# Persistence (concern: database)
conn = psycopg2.connect('...')
cursor = conn.cursor()
cursor.execute(
'INSERT INTO users (id, name, email) VALUES (%s, %s, %s)',
(user_id, name, email)
)
conn.commit()

# Logging (concern: audit)
print(f"User created: {user_id}")

# Response (concern: HTTP)
return {'id': user_id}, 201

# ✅ SEPARATED CONCERNS - Each layer handles its aspect
class EmailValidator:
@staticmethod
def validate(email):
"""Validation concern only."""
return email and '@' in email and '.' in email.split('@')[1]

class UserRepository:
def __init__(self, db_connection):
self.db = db_connection

def save(self, user):
"""Persistence concern only."""
cursor = self.db.cursor()
cursor.execute(
'INSERT INTO users (id, name, email) VALUES (%s, %s, %s)',
(user.id, user.name, user.email)
)
self.db.commit()

class UserService:
def __init__(self, repository, validator):
self.repository = repository
self.validator = validator

def create_user(self, name, email):
"""Business logic concern only."""
if not self.validator.validate(email):
raise ValueError("Invalid email")
user = User(id=str(uuid.uuid4()), name=name, email=email)
self.repository.save(user)
return user

class UserController:
def __init__(self, service, logger):
self.service = service
self.logger = logger

def create_user(self, request):
"""HTTP handling concern only."""
data = request.json
try:
user = self.service.create_user(data['name'], data['email'])
self.logger.info(f"User created: {user.id}")
return {'id': user.id}, 201
except ValueError as e:
return {'error': str(e)}, 400

When to Use / When Not to Use

✓ Apply Separation When

  • Different concerns need independent testing
  • Multiple teams will work on different aspects
  • Concerns are likely to change at different rates
  • Architecture includes multiple layers (web, business, data)
  • Code handles multiple cross-cutting concerns

✗ Avoid Over-Separation When

  • Creating modules with minimal responsibility
  • Splitting concerns that change together
  • Adding abstraction layers without clear benefit
  • Building for separation itself, not real needs
  • Simple scripts or small projects

Patterns and Pitfalls

Pitfall: Separation Without Cohesion

Separating concerns while leaving unrelated code in the same module defeats the purpose. Separation and cohesion work together.

Pattern: Layered Architecture

Organize code into layers where each handles distinct concerns:

  • Presentation layer: HTTP, user interface
  • Business logic layer: domain rules, calculations
  • Data access layer: persistence, queries

Pattern: Dependency Injection

Pass dependencies to modules rather than having them create dependencies internally. This maintains separation while allowing flexibility.

Design Review Checklist

  • Does this module handle multiple unrelated concerns?
  • Are different concerns mixed in the same code?
  • Could this module be tested independently?
  • Does the module change for only one reason?
  • Is clear layering or organization visible?
  • Do changes in one concern force changes elsewhere?
  • Is the boundary between concerns clear?
  • Is the separation justified or is it over-engineered?

Self-Check

  1. Can you identify the main concerns in your application? Are they separated or tangled?

  2. If you change how you store data, how many files need updating? More than one suggests poor separation.

  3. Can you test business logic without setting up databases or HTTP servers?

info

One Takeaway: Organize code so each module addresses one aspect of functionality. When business logic is tangled with HTTP handling and persistence, changes ripple everywhere. Clear separation makes systems easier to test, modify, and understand.

Next Steps

References

  1. Dijkstra, E. W. (1974). On the role of scientific thought. The Essential Edsger W. Dijkstra.
  2. Martin, R. C. (2008). Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall.
  3. Newman, S. (2015). Building Microservices: Designing Fine-Grained Systems. O'Reilly Media.
  4. Fowler, M. (2002). Patterns of Enterprise Application Architecture. Addison-Wesley Professional.