← Back to list

Scaling Business Logic: Enterprise Design Patterns and the Data Mapper

When building small applications, standard MVC (Model-View-Controller) frameworks and basic CRUD operations are usually enough. But as…

Ivan Málaga · 2026-06-18 04:05 · 3 claps · 3.5 min read
#python #data-mapper #backend-development
Open on Medium ↗
Wiki topics: STP · Startups & Venture 🌐 · Web Development

Scaling Business Logic: Enterprise Design Patterns and the Data Mapper

When building small applications, standard MVC (Model-View-Controller) frameworks and basic CRUD operations are usually enough. But as systems grow into enterprise-level platforms, business rules become complex, data sources multiply, and simple architectures begin to crumble under their own weight.

This is where Martin Fowler’s seminal book, Patterns of Enterprise Application Architecture (PofEAA), becomes essential reading. Fowler categorizes the architectural patterns that help developers manage complex business logic and data access.

Today, we will explore one of the most powerful structural patterns from the catalog: the Data Mapper. We will look at why it’s critical for complex systems and implement a real-world example in Python.

The Problem: The Active Record Trap

Many popular web frameworks (like Django or Ruby on Rails) use the Active Record pattern. In Active Record, your database table directly maps to a class, and an instance of that class represents a row. The class contains both the business logic and the database access logic (e.g., user.save()).

For simple apps, this is fantastic. But for enterprise applications, it creates a massive problem: Tight Coupling.

Imagine we are building the analytics engine for a Predictive Citizen Security Platform. Our Incident object doesn't just hold data; it calculates risk scores, determines patrol optimizations, and correlates with historical crime data. If we use Active Record, our complex domain logic becomes hopelessly tangled with SQL queries and database schemas. If the database schema changes, our business logic breaks.

The Solution: The Data Mapper Pattern

Fowler defines the Data Mapper as: “A layer of Mappers that moves data between objects and a database while keeping them independent of each other and the mapper itself.”

With a Data Mapper, your domain objects (your business logic) have absolutely zero knowledge of the database. They don’t inherit from ORM classes, and they don’t have .save() methods. They are pure Python objects. The Data Mapper handles the dirty work of translating those pure objects into database rows, and vice versa.

Real-World Example in Python

Let’s implement this for our security platform. We want to separate our pure Incident domain model from the SQLite database that stores it.

  1. The Pure Domain Model

This is our business logic. Notice there is no SQL, no database imports, and no ORM base classes. It is completely decoupled and easily testable.

# domain/models.py
from datetime import datetime

class Incident:
    """
    Pure Domain Object representing a security incident.
    It contains business rules, NOT database logic.
    """
    def __init__(self, incident_id: str, incident_type: str, severity: int, location: str):
        self.incident_id = incident_id
        self.incident_type = incident_type
        self.severity = severity
        self.location = location
        self.reported_at = datetime.now()
        self.is_resolved = False

    def escalate(self):
        """Business logic rule"""
        if self.severity < 5:
            self.severity += 1

    def resolve(self):
        """Business logic rule"""
        self.is_resolved = True

2. The Data Mapper

This class acts as the mediator. It knows about the database schema and it knows about the domain object, but neither the database nor the domain object knows about it.

# infrastructure/mappers.py
import sqlite3
from domain.models import Incident

class IncidentDataMapper:
    """
    Moves data between the database and the Incident domain object.
    """
    def __init__(self, db_connection_string: str):
        self.conn = sqlite3.connect(db_connection_string)
        self._create_table_if_not_exists()

    def _create_table_if_not_exists(self):
        cursor = self.conn.cursor()
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS incidents (
                id TEXT PRIMARY KEY,
                type TEXT,
                severity INTEGER,
                location TEXT,
                resolved BOOLEAN
            )
        ''')
        self.conn.commit()

    def insert(self, incident: Incident):
        """Translates the Domain Object into a Database Row"""
        cursor = self.conn.cursor()
        cursor.execute('''
            INSERT INTO incidents (id, type, severity, location, resolved)
            VALUES (?, ?, ?, ?, ?)
        ''', (
            incident.incident_id, 
            incident.incident_type, 
            incident.severity, 
            incident.location, 
            incident.is_resolved
        ))
        self.conn.commit()

    def find_by_id(self, incident_id: str) -> Incident:
        """Translates a Database Row back into a pure Domain Object"""
        cursor = self.conn.cursor()
        cursor.execute('SELECT * FROM incidents WHERE id = ?', (incident_id,))
        row = cursor.fetchone()

        if not row:
            return None

        # Reconstruct the pure domain object
        incident = Incident(
            incident_id=row[0],
            incident_type=row[1],
            severity=row[2],
            location=row[3]
        )
        incident.is_resolved = bool(row[4])
        return incident

3. Putting it Together (The Application Service)

Now, in our main application flow, we use the pure domain object to handle the business rules, and the mapper to handle persistence.

# main.py
from domain.models import Incident
from infrastructure.mappers import IncidentDataMapper

def main():
    # 1. Initialize our Data Mapper
    mapper = IncidentDataMapper('security_patrol.db')

    # 2. Create a pure Domain Object
    new_incident = Incident(
        incident_id="INC-2026-001",
        incident_type="Suspicious Activity",
        severity=3,
        location="Mercadillo Bolognesi"
    )

    # 3. Execute Business Logic (Notice how this has nothing to do with DBs)
    print(f"Original Severity: {new_incident.severity}")
    new_incident.escalate()
    print(f"Escalated Severity: {new_incident.severity}")

    # 4. Persist the state using the Mapper
    mapper.insert(new_incident)
    print("Incident persisted to database.\n")

    # 5. Retrieve and verify
    retrieved_incident = mapper.find_by_id("INC-2026-001")
    print(f"Retrieved from DB - Type: {retrieved_incident.incident_type}, Severity: {retrieved_incident.severity}")

if __name__ == "__main__":
    main()

The Enterprise Payoff

At first glance, creating a separate mapper class might seem like unnecessary boilerplate compared to simply calling Incident.save(). But in enterprise software, this separation is a superpower.

By isolating the domain model:

  1. Testing is Trivial: You can unit test your complex business rules (like calculating predictive patrol routes) instantly, without spinning up a test database or mocking SQL connections.
  2. Database Agnosticism: If the city decides to migrate from SQLite to PostgreSQL or even a NoSQL database, your domain/models.py doesn’t change by a single comma. You only write a new Mapper.
  3. Focused Logic: Your developers can focus entirely on solving business problems without being distracted by infrastructure implementation details.

Martin Fowler’s enterprise patterns remind us that the database is merely a storage mechanism — an implementation detail. Your domain logic is the true heart of your software. Protect it with a Data Mapper.


메타데이터
post_id
c413ff7d7a76
slug
scaling-business-logic-enterprise-design-patterns-and-the-data-mapper-c413ff7d7a76
url
https://medium.com/@im2021071086/scaling-business-logic-enterprise-design-patterns-and-the-data-mapper-c413ff7d7a76
canonical_url
https://medium.com/@im2021071086/scaling-business-logic-enterprise-design-patterns-and-the-data-mapper-c413ff7d7a76
author_url
https://medium.com/@im2021071086
status
ok
fetched_at
2026-07-15 07:36:08