Domain-Driven Design (DDD) was first presented by Eric Evans in Domain-Driven Design: Tackling Complexity in the Heart of Software. It is not a new concept by any means, but it remains highly relevant, especially when architects are navigating large, evolving systems.
Imagine sitting at a table that has hundreds of LEGO pieces thrown on top of it. There are hundreds of pieces with different colors and shapes. There are also generic doors and windows, some pieces look like they belong to something specific like the sign of a fire station, others look like they belong to a train station, and several seem to be road pieces. You are then asked to build a city from them. The original boxes are gone; there are no pictures or instructions. Where do you even start?

To many Salesforce Architects, this is what the beginning of a large transformation program feels like. As requirements are gathered, teams often jump to conclusions about which features and licenses to use and what objects to use for different purposes, all before the underlying business model is clear.
You are probably familiar with the experience of reviewing an org where several key objects have similar fields and conflicting automations running on top, often because different teams share the objects but have different needs. Things partially work, but no one can clearly explain why the objects are structured the way they are or who is responsible for them. Changes often result in something breaking for another team. This usually happens when teams build without a clear understanding of the layout of the city.
This scenario illustrates exactly the type of architecture drift the Salesforce Well-Architected Framework aims to prevent by emphasizing clear boundaries, ownership, and solutions that remain maintainable as they evolve.
DDD provides a powerful way to define the city’s layout before deciding which bricks to use.
Domain-Driven Design: Tackling Complexity in the Heart of Software
This foundational book by Eric Evans that teaches how to build complex software by aligning the design with the deep structure of the business domain. It emphasizes collaboration between technical and domain experts, a shared ubiquitous language, and strategic modeling to create maintainable systems that deliver business value.



Understand the city before snapping bricks together
To start thinking in DDD you must first ask yourself: what problem space are we actually in? In building brick terms, are you building a hospital, a train station, or a shopping district?
For Salesforce Architects, this translates into identifying domains before touching object models or thinking about which licenses to use. A domain represents a business capability. A business capability, here, is simply what the business does (e.g., “Asset Lifecycle Management”) rather than how Salesforce does it (e.g., “Update an asset record”).
Core domains
Core domains deserve the most attention as business capabilities that represent the core aspects of the program and justify long-term stakeholder support. They provide a competitive or operational advantage. This is the area in which you will typically start identifying a high demand for customization and where most of the business logic will live.
Example: In an ITSM solution, Incident Managementis a core domain. It’s where your complex Service Level Agreement (SLA) management, automated routing, and specialized UI live.
Supporting domains
Supporting domains enhance the core capabilities, but are not the focus of attention. If the company decided to stop investing in these areas, the impact would be limited.
Example: In an ITSM solution, Knowledge Managementor Survey Management. They enhance the Incident process but aren’t the primary reason the system exists; they are not key differentiators in the business process.
Generic domains
Finally, generic domains, as implied by the name, are those capabilities that are generic in nature and when implemented do not drive the business logic. While they can also be important, they are replaceable and do not require any special business knowledge to understand or implement them. Typical examples include monitoring, logging, and notifications.
From a Well-Architected perspective, this is the foundation for modularity: separating what is core, what is commodity, and what should remain easily replaceable. Once these domains are clearly identified, the LEGO city stops looking like a pile of pieces and begins to look more like a collection of sets.
Create a shared language
One common challenge in long-lived programs is translation debt, the hidden cost of consistently overlooking or ignoring differences in language, context, or assumptions between teams. Business teams often talk about outcomes and may use similar terms to describe different things. Developers work with details such as code, objects, and fields.
It is a common mistake to view architects as translators who sit between departments to bridge these gaps. Relying on a single translator creates a bottleneck and can lead to translation debt. When only the architect understands the mapping between a business goal and a technical solution, the project becomes fragile. In a high-performing team, any engineering team member should be able to make the translation.
This semantic drift occurs when business says “request,” ITSM says “incident,” and engineering builds a “case”. These misalignments are not just minor naming differences. They result in requirement confusion, field proliferation, and “Flow/Apex spaghetti.”
DDD’s idea of a ubiquitous language solves this in a practical way. Teams agree on terms that reflect the business and use them everywhere. These terms become a shared vocabulary across project team members irrespective of their function.
This shared language then translates into technical decisions.
Here is how you can begin developing a ubiquitous language for your team:
Use EventStorming
EventStorming is one way to explore and understand a business through events and flow. Other techniques may be used, but the key goal is always to achieve a shared understanding of the problem space (domain) and its boundaries.
Example: Using EventStorming you gather the domain experts around a wall (or digital board). The ideal mix is people who do the work, people who own the outcomes, and people who will build the solution. Start by discussing “What actually happens in the business?”. Chronologically list the business events in past tense using sticky notes. For instance, Incident Logged, Purchase Order Approved, Asset Provisioned.
Next, use separate colors to identify the actions (Commands) that trigger those events, and the actors (people or systems) responsible for them. This activity will naturally surface the business rules required for the system to function and align the team on the end-to-end flow. Because the focus is on events rather than data structures, it shifts the conversation from how a system is built to what happens and why.
Create a living data and metadata dictionary
Maintain a searchable document that maps every business term to its Salesforce API or technical name. This is the “source of truth” for all stakeholders.
Example: The business may use the term “Service Request” for any work submitted to IT, while in Salesforce it is implemented as a Case with specific record types. A living data dictionary helps clarify how “Service Request” differs from an “Incident,” defining lifecycle states in business terms, and referencing the relevant API names. This prevents semantic drift as teams grow and integrations expand. When someone new joins, there is no confusion about terminology.
Use intentional metadata descriptions
Use the “Description” field on every object, field, and other metadata to state the domain-specific definition. Without this, there is ambiguity on the importance of a given entity, in particular when objects are shared between different functional areas.
Example: Asset.Health_Score__c:
Asset Maintenance – This field stores the physical health rating of an equipment piece based on the most recent field audit.
Implement domain-aligned Apex
Apex code should consistently refer to business terminology through a Service Layer. This ensures that when a developer looks at the code, they are thinking about the business process (for example, resolving an incident) rather than a database operation (for example, updating a Case). It prevents “logic leaking” from other domains into the Incident logic.
Example: Use IncidentManagementService.resolveIncident(Id incidentId) instead of CaseService.updateStatus(Id caseId).
Enforce language through UI alignment
The UI should guide users to act within the defined domain.
Examples:
- Use Verbs Instead of CRUD operations: If you are configuring an Incident page use a button labeled Declare Major Incident, rather than using one labeled “Edit” and expecting the user to manually change the correct priority.
- Use Domain-Driven layouts: While field visibility will ultimately depend on the user’s persona/permission sets, when configuring a page layout you must also ensure the right information is shown in the correct business context. For example, financial information like the Purchase Order number of an Asset will be unnecessary when viewed by a Service Desk agent but it should be at the top and easily accessible to someone working in procurement. Dynamic Forms can be useful in these scenarios.
The most effective way for architects or technical leads to enforce adherence to the language is during backlog refinement. If a user story uses a non-standard term (e.g., “Agreement” instead of “Subscription,” or “Case” instead of “Incident”), it must be flagged for “semantic refactoring.” It is significantly cheaper to change the wording in a user story than to rename a custom object or change code logic three months into production.
By creating a ubiquitous, shared language, you create a system where the metadata, the code, and the UI act as a cohesive reflection of the business. This makes the solution more resilient to change and easier for new team members to navigate.
Design domains with events
Learn how event-driven architecture helps define clear domain boundaries, reduce coupling, and model interactions between systems explicitly. Apply DDD principles through scalable, loosely connected services.



Apply bounded contexts to influence Salesforce architecture
In his classic book The Mythical Man-Month, Fred Brooks wrote that “conceptual integrity is the most important consideration in system design.”
Without clear boundaries and shared language, conceptual integrity—the coherence and unity of a system’s design vision—becomes fragile. DDD gives architects a strategy to protect it.
Bounded contexts are where DDD becomes practical for a Salesforce solution. Inside a bounded context, a specific model and language apply. Outside it, assumptions change.
For example, “Employee” in HR differs from “Employee” in ITSM. HR manages the employment lifecycle. ITSM manages access and assets. The term is the same but the meaning and business rules are not. The same term has different consequences in different contexts.
Once boundaries are clear, they should influence architectural decisions:
Org strategy
Do compliance requirements and operations conflict? Consider if a multi-org strategy would make more sense. When two domains have fundamentally different regulatory obligations, security models, release cadences, or data ownership rules, forcing them into a single org can create constant friction. Separate orgs can protect conceptual integrity, reduce unintended coupling, and allow each context to evolve safely without destabilizing the other.
Packaging strategy
Does this domain have high volatility? If so, it should live in its own Unlocked Package. This creates a physical boundary that prevents other domains from being affected when it is replaced or entirely refactored.
It is worth pausing briefly on what “volatility” means here. Volatility-based decomposition is the idea that architectural boundaries should follow change patterns. If a capability evolves frequently due to regulatory updates or ongoing rapid change, it should be isolated so that its evolution does not affect the rest of the system. Some authors frame this as an alternative to functional decomposition, arguing we should organize systems around change rather than purpose. I see them as complementary: functional boundaries define intent; volatility boundaries contain impact.
Integration patterns
Should we use a tightly-coupled schema or event-driven handoff? If two contexts are highly independent, use Platform Events to communicate changes without coupling their data models. When one domain begins directly querying or changing another domain’s objects, their boundaries can erode over time and create hidden dependencies. Change in one area can then introduce unpredictable side effects in the other. Event-driven integration preserves autonomy by allowing each context to react to business events on its own terms, maintaining separation while still enabling coordination through explicit, well-defined interactions.
Build versus buy
Generic domains, such as logging or notifications, should leverage standard solutions. For example, instead of building a custom logging framework, use an open-source tool like Nebula Logger. Similarly, for a supporting capability like shipment tracking, use a managed package rather than trying to come up with your own implementation that needs to integrate with multiple couriers.
Enforcing boundaries
Defining domains and contexts is not useful if the definition stays only in a diagram and is not enforced during implementation.
Here are some approaches for enforcing boundaries within domains:
Use Event-Driven decoupling
The idea here is to eliminate direct interaction between packages by using an asynchronous message bus. For example, instead of the Asset Management package implementing a scheduled automation querying employee records for laptop provisioning, the HR package publishes an “Employee Hired” event to which the Information Technology Asset Management (ITAM) package is subscribed. When it receives the signal, the ITAM package executes its own internal logic to provision a laptop and peripherals based on the department provided.
Expose services and hide the schema
Packages should not directly query or modify each other’s objects. Allowing this while having multiple unlocked packages creates a false sense of modularity, when in reality, you are creating a distributed monolith that breaks easily. Access to package data should happen through an Apex Service Layer, or even better, through an interface owned by the consuming package using dependency inversion. This gives you the freedom to change the internals of a package without breaking external functionality.
Build an Anti-Corruption Layer (ACL)
DDD uses the concept of having a protective layer when integrating with an external system, where you do not let the details of that system leak into your core logic. Think of the ACL as a specialized Adapter pattern that ensures that the API messages your Salesforce system interacts with are speaking the same business language.
For example, imagine you are integrating an external monitoring tool with your Salesforce solution. The monitoring tool might send a cryptic alert with code: DSK_84. You do not want your Apex code or Flows to be filled with logic that checks for these specific numbers. Your ACL, usually handled in a tool like Mulesoft, catches this and maps it to a clean internal value like “Critical Disk Space Warning.” This keeps your Salesforce logic focused on the actual business process of incident resolution instead of deciphering technical codes from another system.

Take the first step toward domain clarity
Before your next Salesforce project begins modeling objects or automation, pause. Identify core, supporting, and generic domains. Run a structured discovery session using EventStorming or another collaborative approach. Validate that your packaging, automation, and integration strategy reflect meaningful boundaries. Be consistent in your business language, which should not stay in slides but also live in the metadata dictionary, data models, and Apex logic.
One of the most common phrases architects hear or say is “Let’s make sure this solution is future-proof.” A better goal is “future-ready.” Thinking through the lens of Domain-Driven Design and applying volatility-based decomposition makes a system more adaptable to change.
Designing with the future in mind does not mean implementing–or even anticipating–every possible requirement today. It means structuring the solution so that future requirements can be absorbed without destabilizing the core.
That requires mapping the city before placing the bricks.

Align architecture to your domain
Composable architecture brings Domain-Driven Design to life by organizing Salesforce solutions around clear domain boundaries and cohesive business capabilities. When your technical components reflect your business domains, you gain autonomy, clarity, and the freedom to evolve without breaking everything else.







