Modularity Anti-Patterns

Overview

This document identifies common anti-patterns in modular Salesforce architectures based on real-world refactoring experiences. Each anti-pattern includes the problem, why it occurs, its impact, and proven solutions.

1. The Monolithic Package Anti-Pattern

The Intent Behind Modularity

The whole point of package-based architecture is to enable independent development, testing, and deployment of features. When everything lives in one package, you lose these benefits entirely. Think of it like building a house where every room's electrical system is connected - you can't work on the kitchen wiring without shutting down the entire house.

Problem

A single package grows to contain hundreds of components, mixing multiple domains and responsibilities. What starts as a "core" package becomes a dumping ground for everything.

How It Manifests

src/
└── mega-package/
    ├── classes/           # 500+ classes: OrderService, CustomerService, 
    │                     # InventoryManager, TaxCalculator, EmailHandler...
    ├── objects/          # 50+ objects from unrelated domains
    ├── flows/            # Flows for customer onboarding, order processing,
    │                     # inventory management, reporting...
    └── lwc/              # UI components for every feature in the system

Why This Is Problematic

Imagine you need to fix a bug in the tax calculation logic. In a monolithic package:

  1. You deploy the entire package (30+ minutes)

  2. All tests run (45+ minutes)

  3. Any team working on ANY feature is blocked

  4. If something breaks, you rollback EVERYTHING

  5. You can't give a client just the tax fix - they get all pending changes

The Real Cost

Solution: Domain-Driven Package Decomposition

The solution is to break apart the monolith based on business capabilities, not technical layers. Each package should represent something a business person would understand.

Now that tax rate change:

  • Deploys in 2 minutes (just tax-management package)

  • Only runs tax-related tests

  • Other teams continue working

  • Can be deployed to specific orgs

  • Rollback affects only tax functionality

2. The False Modularity Anti-Pattern

The Intent We're Violating

True modularity means packages can be understood, developed, tested, and deployed in isolation. When packages secretly depend on each other through the database or configuration, they're lying about being modular.

Problem

Packages appear modular but are tightly coupled through hidden dependencies. It's like having "separate" apartments that share plumbing - turn off water in one, and the neighbor's shower stops working.

How It Manifests

Why This Is Dangerous

The code above creates an illusion of modularity. The packages seem independent but:

  • You can't test PricingService without inventory package's data

  • Deploying inventory changes can break pricing without warning

  • There's no contract defining what Category__c values are valid

  • The dependency is invisible in package manifests

The Hidden Coupling Problem

Solution: Explicit Contracts and Ownership

Make dependencies visible and controlled through interfaces:

3. The Circular Dependency Maze

The Intent of Dependency Management

Dependencies should flow in one direction, typically from higher-level packages (business logic) to lower-level packages (utilities, data access). When packages depend on each other circularly, it's like two people holding doors open for each other - nobody can actually go through.

The Problem Visualized

Real Code That Creates Circles

Why Circular Dependencies Are Deadly

  1. Can't deploy: Package A needs B, B needs C, C needs A... infinite loop

  2. Can't test: Mocking becomes impossible when everything depends on everything

  3. Can't understand: Where does the logic actually live?

  4. Can't refactor: Moving anything breaks everything

Solution: Dependency Inversion and Events

Break the cycle by introducing abstractions and using events for loose coupling:

4. The Chatty Packages Anti-Pattern

The Intent of Service Boundaries

Package interfaces should be coarse-grained - think of them like international phone calls. You wouldn't call another country to ask one word at a time; you'd have a complete conversation. Same with packages.

Problem Illustrated

Why Chattiness Kills Performance

Each cross-package call has overhead:

  • Parameter marshalling

  • Service location/injection

  • Security checks

  • Logging/monitoring

  • Error handling

Multiply that by 500 and you have:

  • Slow performance

  • Governor limit issues

  • Difficult debugging

  • Complex test setup

Solution: Bulk Operations and Aggregated Interfaces

Design interfaces that accept collections and return complete results:

5. The Configuration Coupling Anti-Pattern

The Intent of Package Independence

Each package should own its configuration and not be affected by other packages' configuration changes. Shared configuration is like sharing a toothbrush - it seems convenient until someone changes how they use it.

The Hidden Configuration Problem

Why Shared Configuration Breaks Modularity

  1. No ownership: Who's responsible for EmailFromAddress__c?

  2. Hidden dependencies: Package manifest doesn't show config dependencies

  3. Runtime surprises: Config changes break unrelated packages

  4. Testing nightmare: Tests need specific config states

Solution: Package-Specific Configuration

Each package owns its configuration with clear namespacing:

6. The Overly Generic Package Anti-Pattern

The Intent of Domain Focus

Packages should solve specific problems well, not all problems poorly. It's better to have a sharp knife and a good screwdriver than a dull Swiss Army knife.

The Generic Monster

Why Generic Packages Fail

  1. Impossible to test: Every change requires testing all paths

  2. Impossible to understand: What does this package actually do?

  3. Impossible to maintain: Where do you add new functionality?

  4. Terrible performance: Constant branching and type checking

  5. No clear API: What parameters are valid? Who knows!

Solution: Domain-Specific Packages

Create focused packages with clear purposes:

Key Takeaways

The Business Test

Can you explain what a package does to a non-technical stakeholder?

  • ✅ "This handles tax calculations"

  • ❌ "This processes various operations on multiple object types"

The Deployment Test

Can you deploy a package independently without breaking others?

  • ✅ Deploy tax changes without touching orders

  • ❌ Deploy mega-package and pray nothing breaks

The Team Test

Can two teams work on different packages without conflicts?

  • ✅ Team A on orders, Team B on inventory, no merge conflicts

  • ❌ Everyone fighting over the same files

The Understanding Test

Can a new developer understand a package's purpose in 5 minutes?

  • ✅ "order-management does... order management"

  • ❌ "universal-processor does... everything?"

Conclusion

These anti-patterns emerge naturally as systems grow. The key is to:

  1. Recognize them early through metrics and code reviews

  2. Refactor incrementally - you don't have to fix everything at once

  3. Prevent recurrence through architecture governance

  4. Balance modularity with practicality - some coupling is acceptable

Remember: The goal isn't perfect modularity - it's maintainable, understandable, and deployable systems that teams can work on without stepping on each other's toes.

Last updated

Was this helpful?