Skip to main content

Pure Fabrication

Create artificial classes to represent operations when domain objects can't handle them

TL;DR

Pure Fabrication is a pattern that addresses situations where assigning a responsibility to an Information Expert would violate Low Coupling or High Cohesion. You create an artificial class (one not based on domain concepts) that represents a conceptual operation or service. This keeps domain classes focused while handling complex cross-cutting concerns.

Learning Objectives

  • Understand when Information Expert would create design problems
  • Learn to identify situations requiring Pure Fabrication
  • Recognize the difference between domain classes and service classes
  • Create service classes that maintain Low Coupling and High Cohesion
  • Balance between domain-driven design and practical architecture

Motivating Scenario

You need to save Order objects to a database. Information Expert says the Order should save itself (it has the data). But if Order depends on a database library, every Order instance couples the domain logic to persistence technology. Pure Fabrication suggests creating an OrderRepository or OrderPersister class that knows about databases but handles persistence, freeing Order to focus on business logic.

Core Concepts

Pure Fabrication is a compromise between strict Information Expert and practical design constraints. It says: when assigning a responsibility to an Information Expert would violate Low Coupling or High Cohesion, create an artificial class that doesn't represent a domain concept but represents a needed operation.

Pure Fabrication classes are typically:

  1. Service Classes: Encapsulate operations that don't naturally belong to domain objects
  2. Repository/Persistence Classes: Handle data storage and retrieval
  3. Utility Classes: Perform calculations or transformations
  4. Adapter Classes: Bridge different technologies or interfaces
  5. Factory Classes: Handle complex object creation

The pattern trades "purity" (every class represents a domain concept) for practical benefits: Low Coupling, High Cohesion, and clear separation of concerns. A good Pure Fabrication class has a clear, single responsibility and uses patterns like Singleton, Factory, or Strategy.

Pure Fabrication: When Information Expert Isn't Enough

Practical Example

Let's see how Pure Fabrication handles persistence:

pure_fabrication_example.py
# INFORMATION EXPERT (creates coupling)
class BadOrder:
"""Mixes domain logic with persistence"""
def __init__(self, order_id: str, customer: str):
self.order_id = order_id
self.customer = customer
self.total = 100.0

def save(self):
# Order knows how to save itself - but now it's coupled to DB
import sqlite3
conn = sqlite3.connect("orders.db")
# ... persistence logic ...

# PURE FABRICATION (separates concerns)
class Order:
"""Pure domain class - no persistence knowledge"""
def __init__(self, order_id: str, customer: str, total: float):
self.order_id = order_id
self.customer = customer
self.total = total

def apply_discount(self, percent: float):
self.total *= (1 - percent / 100)

class OrderRepository:
"""Pure Fabrication: handles persistence concerns"""
def __init__(self, db_connection):
self.db = db_connection

def save(self, order: Order) -> bool:
"""Save an Order to the database"""
try:
self.db.execute(
"INSERT INTO orders (id, customer, total) VALUES (?, ?, ?)",
(order.order_id, order.customer, order.total)
)
return True
except Exception as e:
print(f"Error saving order: {e}")
return False

def load(self, order_id: str) -> Order:
"""Load an Order from the database"""
cursor = self.db.execute(
"SELECT * FROM orders WHERE id = ?",
(order_id,)
)
row = cursor.fetchone()
if row:
return Order(row[0], row[1], row[2])
return None

# Usage
import sqlite3
db = sqlite3.connect(":memory:")
db.execute("""
CREATE TABLE orders (
id TEXT PRIMARY KEY,
customer TEXT,
total REAL
)
""")

# Order stays clean and focused
order = Order("ORD-001", "John", 150.00)
order.apply_discount(10)

# Repository handles persistence
repo = OrderRepository(db)
repo.save(order)

# Load it back
loaded_order = repo.load("ORD-001")
print(f"Loaded: {loaded_order.customer}, ${loaded_order.total:.2f}")

When to Use / When Not to Use

Use
  1. When Information Expert would require domain objects to know about infrastructure
  2. For persistence, logging, or other cross-cutting concerns
  3. When you need a service to coordinate multiple domain objects
  4. For factory methods that are too complex to put in domain objects
  5. When external technology dependencies need isolation
Avoid
  1. For every operation - keep most logic in domain objects
  2. Creating service classes that duplicate domain logic
  3. Over-fragmenting - not every small operation needs a fabricated class
  4. Making fabricated classes too complex or too many
  5. Losing domain knowledge in translation to service classes

Patterns and Pitfalls

Pure Fabrication Implementation

Create for clear needs: Use Pure Fabrication when Information Expert would create coupling problems. OrderRepository makes sense because orders shouldn't know about databases.

Keep them focused: A fabricated class should have one clear responsibility. OrderRepository handles persistence for orders; it doesn't handle authentication or validation.

Name them clearly: Use names like Repository, Service, Factory, or Manager to signal that these are fabricated, not domain classes.

Anemic domain models: Don't push all logic into service classes, leaving domain objects as empty data containers. Domain objects should have behavior.

Too many service classes: If you find yourself creating a service for every operation, you've over-fragmented the design. Let domain objects do their job first.

Service-to-service dependencies: If service classes depend on many other services, you've likely created artificial hierarchy that's hard to understand.

Design Review Checklist

  • Would assigning this to a domain object create coupling problems?
  • Does this fabricated class have a single, clear responsibility?
  • Is it named appropriately (Repository, Service, Factory, etc.)?
  • Does it use domain objects, not duplicate their logic?
  • Could domain objects handle this responsibility if coupling wasn't an issue?
  • Does this class reduce overall complexity and improve design clarity?

Self-Check

  1. What's the difference between Information Expert and Pure Fabrication? Information Expert assigns responsibilities to classes with the information. Pure Fabrication creates artificial classes when Information Expert would violate coupling or cohesion principles.

  2. When should you create a Pure Fabrication class? When a responsibility would require domain objects to depend on infrastructure, external systems, or have multiple unrelated concerns.

  3. What makes a good fabricated class? A clear, single responsibility, appropriate naming (Service, Repository, Factory), and focus on supporting domain objects rather than duplicating their logic.

info

One Takeaway: Use Pure Fabrication pragmatically. When assigning a responsibility to a domain object would create coupling or complexity problems, create a specialized service class to handle it.

Next Steps

References

  1. GRASP (Object-Oriented Design) - Wikipedia ↗️
  2. Applying UML and Patterns by Craig Larman ↗️