Read about our update schedules here.
Composable solutions adjust quickly and with greater stability. A system architected to be composable is built in meaningful units, or building blocks, that can operate gracefully with one another and can be easily swapped in and out of service. Building composability into a system enables you to introduce new features or remove technical debt by refactoring or reassembling individual units of a system. Composability enables faster, more predictable delivery cycles and releases, as teams can focus on building and delivering meaningful features via smaller amounts of change.
Composable systems make it possible for businesses to adapt more quickly and with greater stability — whether the stimulus is internal to the business or caused by external factors. Composability helps systems be more resilient and can help make solutions and architectural patterns more intentional.
You can make your Salesforce solutions more composable by building three key habits: separation of concerns, interoperability, and packageability. Below, you can see the relationship of these habits in two ways: for a single unit of in a composable system, and across multiple units in a composable system.
A single, composable unit:
A composable system:
A fundamental concept in software engineering and system architecture, separation of concerns entails identifying various concerns within a larger system that can be separated into modular units. Achieving a strong separation of concerns within a system requires the creation of meaningful units for application logic and functions throughout the system. Smaller units make it easier for app delivery and maintenance teams to understand how and where to make changes, with minimal disruption to the larger system. Smaller units also make it clearer how and where to focus work when addressing technical debt and contribute to the overall readability of your system.
You can build better separation of concerns into your Salesforce org by orienting to business capability and managing state.
For Salesforce systems, the best approach for separation of concerns applies a business-oriented perspective to identify and organize modular units (or capabilities) within the system. This is unlike an engineering-focused view, which identifies units based on their technical function. A business-oriented perspective in separation of concerns requires organizing the code and customizations in your system based on the service they provide to the business and business users. Taking this approach does not mean ignoring potential for redundant or duplicative technical functions in a system. Rather, it means that technical services are clearly mapped to an organizing principle that is ultimately transparent to the business.
Orienting to business capabilities helps ensure that the hand-offs between business analysis and discovery teams to delivery teams are as straightforward as possible. If app builders have no idea where their work units map into your composable architecture, you have a mess, not a composable app landscape. Unclear mappings between the end state required by the business and the modular units within an org also increase the chances of development teams or vendors building redundant functions into the system.
Consider the following to orient functional units to business capability:
The list of patterns and anti-patterns below shows what proper (and poor) orientation to business function looks like within a Salesforce solution. You can use these to validate your designs before you build, or identify areas of your system that need to be refactored.
To learn more about tools available from Salesforce to help you better orient to business capabilities, see Tools Relevant to Composable.
State management centers on movement of information throughout a system at various points in time. Effective state management enables applications to handle complex flows of data or interactions with a minimum of unplanned or indeterminate outcomes. Managing state in a modular Salesforce org means building clear paths for logic flows within and between units, as well as graceful paths for unplanned execution behaviors. State management makes it possible to form coherent streams of logic from modular Salesforce application units. In most cases, this requires interoperability to structure information flow between units.
Difficulties in managing state across loosely coupled units often lead to monolithic application development in Salesforce. You can see this in large “monoflows", which are built in an attempt to orchestrate a complex process within a singular flow. Another example is massive Apex classes, which orchestrate complex processes through spaghetti code, or a lengthy series of single-use methods. These types of application architectures make refactoring and debugging difficult and increase onboarding times for new team members or vendors. They also reduce the value of the applications delivered to the business, as functionality isn’t reusable.
Consider the following to manage state across a modular Salesforce org:
The list of patterns and anti-patterns below shows what proper (and poor) state management looks like within a Salesforce solution. You can use these to validate your designs before you build, or identify places in your system that need to be refactored.
To learn more about Salesforce tools for managing state, see Tools Relevant to Composable.
The following table shows a selection of patterns to look for (or build) in your org and anti-patterns to avoid or target for remediation.
✨ Discover more patterns for separation of concerns in the Pattern & Anti-Pattern Explorer.
Patterns | Anti-Patterns | |
---|---|---|
Functional Units | In your design standards:
- Naming conventions address how to denote a functional unit - A list of all currently defined functional units (and related naming conventions) exists - Standards for proposing functional unit additions or changes exist |
In your design standards:
- Design standards do not exist or do not deal with functional units and use cases |
In your documentation:
- System landscape diagrams clearly show the functional units in an org - All documentation and implementation diagrams clearly show the functional unit(s) for components - Documentation for individual components include functional unit mapping for the component - All components within a functional unit are searchable and easy to find |
In your documentation:
- Component documentation does not exist - Component documentation describes the functional unit a component belongs to, but that is the only place the definition of that functional unit appears - You cannot search for a particular functional unit and/or searches do not help identify all the components within a functional unit |
|
In your org:
- It is possible to quickly identify functional unit alignment for a given piece of metadata (for example, a flow, Apex class, or Lightning page) - Functional units are labelled in business-friendly terms |
In your org:
- It is not possible to identify functional unit alignment for any metadata - Functional unit information is inconsistent or inaccurate - Functional unit information is labelled in engineering-focused terms that are meaningless to business users |
|
State Management | In your design standards:
- Use cases for stateful vs. stateless designs are clear - Approved patterns for stateless communication exist - Approved patterns for stateful communication exist - Clear categories for state exist |
In your design standards:
- Design standards do not exist or do not deal with state/stateless patterns and use cases |
In your documentation:
- Every component that handles stateful and/or stateless communication indicates what pattern has been implemented - It is possible to search for and find all components that have implemented a particular stateful/stateless pattern - Process and interaction diagrams provide detail about state categories and hand-offs |
In your documentation:
- Component documentation does not exist - Component documentation describes the stateful/stateless pattern implemented, but that is the only place the definition appears - It is not possible to search for a particular pattern and/or searches do not help identify all the components using that pattern |
|
In Apex: - Savepoints and rollback behaviors are used in all data operations |
In Apex: - Savepoints and rollback behaviors are not used |
|
In Flow: - Fault paths and the Roll Back Records element is used |
In Flow: - The Roll Back Records element is not used |
In a system architected for interoperability, components can exchange information and operate together effectively. Interoperability is a key to making a modular system composable rather than siloed. Interoperability can be difficult to achieve in a loosely coupled system. You need to establish standards for consistent methods of integration that don’t undermine the independence between units and overall separation of concerns across the system.
Interoperability also impacts the quality of your user experiences. Your users expect data created in one area (like order information) to be usable in another (like marketing campaigns), and your builders expect that if they learn how to do something (like build a flow, or authenticate to an API) that they’ll find familiar techniques work when they move on to the next problem. Failing to design for interoperability will result in redundant architectures, replicated data, process inefficiencies, and increased development and support costs.
You can create interoperability in modular systems with messaging and eventing as well as with API management.
Messages and events are two ways you can enable components across a system to send and receive information. In terms of the content they can carry, events and messages are similar. A key difference is the behavior of the sender. A component sending a message typically sends data to a specific destination, and awaits some kind of response from the recipient. In contrast, a component emits an event when something has happened. There is no specific destination, nor an expectation of a response. These differences in behavior support different communication patterns. Messages support stateful communication patterns, and events support stateless communication patterns. (See State Management for more about this distinction.)
It’s important to align teams on protocols and use cases for messaging and eventing. Unclear standards can result in a mix of patterns across your system, leading to:
Consider the following when designing messages and events to create more loosely coupled structures within your Salesforce org:
The list of patterns and anti-patterns below shows what proper (and poor) messaging and eventing looks like within a Salesforce solution. You can use these to validate your designs before you build, or identify places in your system that need to be refactored.
To learn more about Salesforce messaging and eventing tools,see Tools Relevant to Composable. For more on choosing an eventing pattern or tool for a given use case, see the Architect’s Guide to Event-Driven Architecture with Salesforce.
Building proper application programming interface (API) management within a Salesforce solution enables individual components of your system follow predictable communication patterns. Salesforce provides built-in, secure APIs to use for communication with systems outside Salesforce. (For more on Salesforce Platform APIs see Architecture Basics.)
Effective API management within Salesforce solutions is key to building truly composable architectures. It enables components inside a Salesforce org to send and receive information efficiently. It also enables solution builders to follow clear protocols for how the components they build communicate with other components in the system, and helps them identify poor implementations early. Further, it enables solution builders to more narrowly focus on the particular component they are building or refactoring, which boosts productivity and quality.
API Management at the enterprise level is a separate topic — and is best achieved with a comprehensive API Management tool. (For more on the MuleSoft perspective on this topic, see What is API Management?.)
Consider the following to build API management capabilities within your Salesforce solutions:
Think of APIs as predictable patterns or contracts for communication. The data type of input or output variables, variable names, the pieces of information that should (or should not) appear in a given pattern — these are the keys to effective API management. These definitions should appear in your design standards, and teams should be able to find out how these definitions have been implemented in particular parts of your system via your documentation. One way to view difficulties merging changes from new development into an integration environment is to look at them as evidence of where you have poorly implemented API communications (or perhaps no API definition at all).
Don’t limit your thinking on APIs to code exclusively. What defines an API in this context is the consistency of the message structures and variables within that message. As long as that consistency is maintained, an API can be implemented in code as well as in declarative customizations, such as modular autolaunched (or platform event-triggered) flows, or even flow templates. Base your decisions around what to define in code versus in flows not on API implementation but rather on packageablility and testability.
Keep your lifecycle and versioning simple. Like all parts of application development, APIs have a lifecycle: define, build, test, deploy, and maintain (including retire). Salesforce Platform APIs release several versions within a calendar year, because of the platform’s rapid release cycle. (You can read more about this behavior in Salesforce Architecture Basics.) This means that platform APIs have a fairly complex lifecycle, as several versions of a particular API need to be maintained and available for Salesforce customers. This level of complexity is appropriate for PaaS use cases — but it is most likely unnecessary complexity for your on-platform solution architectures (see: Readablility anti-patterns). Focus on defining a clear purpose for an API (for example, error handling) and clear baseline definitions. Aim to have only one version of each API.
Make your APIs discoverable, accessible, and manageable. Document your APIs so potential consumers can find available APIs connect to them easily. APIs need to function smoothly as part of a landscape of capabilities.
Be iterative. Focus on defining and implementing only one internal API at a time. That way, you can focus on iterating the API definition rapidly, find the right form for your one supported version and establish best practices from the experience of implementation.
The list of patterns and anti-patterns below shows what proper (and poor) API management looks like within a Salesforce solution. You can use these to validate your designs before you build, or identify areas of your system that need to be refactored.
To learn more about tools available from Salesforce to help you build more interoperability, see Tools Relevant to Composable.
The following table shows a selection of patterns to look for (or build) in your org and anti-patterns to avoid or target for remediation.
✨ Discover more patterns for interoperability in the Pattern & Anti-Pattern Explorer.
Patterns | Anti-Patterns | |
---|---|---|
Messaging and Eventing | In your design standards:
- Clear standards exist for when to use synchronous patterns (messaging) and asynchronous patterns (eventing) - Clear standards exist for event and message structures |
In your design standards:
- Design standards do not exist, or they lack clear standards for sync vs. async patterns and clear standards for message or event structures |
In your org:
- Platform events used for internal system messaging are clearly labelled - Salesforce Flow tools reference system-wide messaging or eventing services - Consistent messaging and eventing patterns appear in flows and code |
In your org:
- Platform events used for internal system messaging are not clearly labelled or do not exist - Different strategies for messaging and eventing patterns appear across flow and code |
|
In Apex or LWC:
- Custom event definitions are limited in scope (no system-wide events or messages are defined in code) - System-wide messaging or eventing services in Apex are annotated in ways that make them available in Salesforce Flow tools |
In Apex or LWC:
- System-wide message and/or event structures are defined in Apex or JavaScript - System-wide event or message structures defined in Apex are not available in tools like flow |
|
API Management | In your design standards:
- Clear protocols for cross-component communication (i.e. APIs) exist - Protocols/APIs are outlined in logical groups that builders can search for and find - Protocols/APIs define variable data types, variable names, what is required or optional and provide a clear description of when to use |
In your design standards:
- Design standards do not exist or do not define APIs and use cases |
In your documentation:
- Every component's documentation clearly lists which API/communication protocol has been implemented - It is possible to search for a particular API or protocol and identify components where it is implemented |
In your documentation:
- Component documentation does not exist - Component documentation describes the API implemented within a component, but that is the only place the API definition appears - It is not possible to search for a particular API or protocol and/or searches do not help identify components where an API or protocol has been implemented |
|
In your org:
- API message formats and variables for internal communication are defined with custom metadata types - API message formats and variables for internal communication are defined with platform events - Code and declarative customizations reference the appropriate custom metadata type (or platform event) in order to send or receive information |
In your org:
- Communication between components of the system (code and declarative customizations) is ad hoc - APIs are defined exclusively for communication between Salesforce and external systems |
Creating packageability in a Salesforce org means functionality in the org comes from units that can be developed and deployed independently and reliably, as packages. Ideally, these units are defined as a type of second-generation package (either an unlocked package or managed package for ISVs). Note: Because well-architected solutions use these package types exclusively, first-generation managed packages are not covered here.
It’s one thing to define separations of concerns and create functional units in a Salesforce org. It’s another thing to untangle and manage dependencies clearly enough to successfully version those units as package artifacts. Achieving that level of stability and metadata isolation requires a significant level of DevOps (and architectural) maturity. It also speeds up development time, improves app builder experiences, and brings predictability and control to releases and release management. Not every organization will be capable of supporting the infrastructure required to define, maintain, and develop effective packages. But achieving packageability should be the ultimate goal for nearly every Salesforce org.
You can build packageability in your Salesforce solutions by focusing on loose coupling and dependency management.
In a system with loose coupling, individual pieces are not strongly tied to one another. Many of the advantages of a composable system stem from loose coupling. In packageable systems, achieving loose coupling between packages (and installing orgs) will enable you to have well-defined packages and more productive development cycles for teams working with packages.
On the Salesforce Platform, you can build packages that are tightly coupled to a particular org. This capability is useful in defining functional units and experimenting with proper separation of concerns in your org, as you progress in packaging maturity. If you choose this approach, however, you will realize few of the benefits of truly packageable metadata, including source-driven development, the ability to use versioning, and artifact stability. Instead, you will likely continue to experience the drawbacks of a tightly coupled system, including:
The end goal for any packageable Salesforce system is loosely coupled packages.
Consider the following as you look at separating your Salesforce metadata into effective packages:
Regardless of how you decide to define your packages, you will only successfully version a loosely coupled package through effective dependency management.
The list of patterns and anti-patterns below shows what proper (and poor) loose coupling looks like for Salesforce packaging. You can use these to validate your designs before you build, or identify areas of your system that need to be refactored.
To learn more about Salesforce tools to help you build more packageability, see Tools Relevant to Composable.
In the context of a Salesforce solution, dependency management means identifying and structuring the relationships between the metadata in your packages and the metadata in the orgs into which those packages will be installed. Dependency management is a key part of developing stable package artifacts.
Dependencies can be at odds with efforts to create perfectly separated, loosely coupled functional units. In an ideal engineering state, a loosely coupled system has no dependencies between units. In the real-world, however, eliminating all dependencies is not practical.
Often, the balance between making systems readable and using business-friendly functional units requires a trade-offs in terms of perfect isolation across the units in a system. More pragmatically, the core services provided by the standard functionality of the Salesforce Platform create key, net-positive dependencies for any Salesforce solution. Many built-in scalability, performance, and security advantages come from how deeply they are integrated into the platform. When designing packageable Salesforce solutions, it’s important to remember that package dependencies are not inherently bad. Poor dependency management is bad.
Effective dependency management with Salesforce packaging means development and maintenance teams can:
The techniques for dependency management with Salesforce are fairly straightforward:
In the end, you will need to decide the design patterns that are allowed in your org across declarative and programmatic development. The most important considerations (architecturally) are to define where you want to add more engineering complexity in order to have fewer dependencies, and where you need to tolerate more dependencies to simplify app builder workflows.
As you build dependency management strategies into your packages, consider the two primary organizing principles for package units: horizontal and vertical.
Note that this is not an either/or decision. You can mix vertical and horizontal paradigms as needed. Often, the fastest way to start with a package is to create horizontal service layers. As the scale and complexity of your package adoption grows, abstracting more vertical units will help simplify complex environment setup processes or developer onboarding.
A system with two functional units (A and B), for example, can be structured with package dependencies arranged in vertical, horizontal, and vertical-horizontal hybrid paradigms.
Regardless of the package organizing paradigm you choose, there are some absolutes to keep in mind:
The list of patterns and anti-patterns below shows what proper (and poor) dependency management looks like with Salesforce packages. You can use these to validate your designs before you build, or identify areas of your system that need to be refactored.
To learn more about Salesforce tools for dependency management, see Tools Relevant to Composable.
The following table shows a selection of patterns to look for (or build) in your org and anti-patterns to avoid or target for remediation.
✨ Discover more patterns for packageability in the Pattern & Anti-Pattern Explorer.
Patterns | Anti-Patterns | |
---|---|---|
Loose Coupling | In your design standards:
- Naming conventions address how to denote package units - It's possible to search for and find a list of all currently defined package units (and related naming conventions) - Standards for proposing package unit additions or changes exist - (Optional) All approved use cases for custom settings are clearly listed (if you have any) |
In your design standards:
- Design standards do not exist or do not deal with package units and use cases |
In your org:
- Custom metadata types provide dynamic, run-time information for code and declarative customizations - No custom settings exist or few custom settings exist, and none are related to packaged functionality - No custom objects exist in order to provide dynamic, run-time information for code or declarative customizations |
In your org:
- Custom settings are used - Custom objects exist in order to provide dynamic, run-time information for code or declarative customizations - Custom metadata types are not used (or are not used consistently) to provide dynamic, run-time information for code and declarative customizations |
|
In Apex:
- Common services and boilerplate code are defined using abstract or virtual Apex classes - Methods dependent on dynamic, run-time information reference appropriate custom metadata types |
In Apex:
- Common services and boilerplate code are not easily distinguished from other classes - Internal references across classes and methods are hard to follow and are inconsistent throughout the codebase - Methods do not use a consistent approach for accessing dynamic, run-time information, or methods query custom objects for runtime behavior information, or code references custom settings |
|
In source control and development environments:
- package.xml files only appear in early stage or proof-of-concept project manifests |
In source control and development environments:
- package.xml files are used to control metadata deployments |
|
In packages:
- Org-dependent unlocked packages are used only for early-stage or proof-of-concept experiments - No unmanaged packages are defined in production or sandboxes |
In packages:
- All packages are org-dependent unlocked packages - Unmanaged packages are defined in production or sandboxes |
|
Dependency Management | In your design standards:
- Standards for declaring dependencies exist - Standards for introducing or modifying dependencies exist |
In your design standards:
- Design standards do not exist or do not deal with how to declare dependencies |
In source control:
- Package versions for unlocked packages use aliasing ( LATEST ) to declare dependencies in sfdx-project.json manifests
- Developers can create scratch orgs and deploy package metadata successfully from source control |
In source control:
- Package versions for unlocked packages are declared explicitly (no LATEST aliasing) in sfdx-project.json manifests
- Developers cannot work successfully with scratch orgs using source control |
|
In your packages:
- No metadata is duplicated across packages - For package development, all early-stage development work happens in scratch orgs |
In your packages:
- Dependencies are circumvented by duplicating metadata in different packages - Early package development happens in developer sandboxes or early package development cannot happen in scratch orgs |
|
Also see: Loose Coupling |
Tool | Description | Separation of Concerns | Interoperability | Packageability |
---|---|---|---|---|
Apex REST Web Services | Expose your Apex classes and methods to external applications via REST | X | ||
Apex SOAP Web Services | Expose your Apex classes and methods to external applications via SOAP | X | ||
Change Data Capture | Publish changes to Salesforce records | X | ||
Custom Metadata Types | Define reusable, customizable, packageable functionality | X | ||
Decorators | Expose functions or properties publically as an api | X | X | |
Dev Hub | Manage scratch orgs, second-generation packages, and Einstein features. | X | X | X |
Generic Events (Legacy)* | Send custom events that are not tied to Salesforce data changes | X | ||
Lightning Data Service | Cache and share data across components | X | X | |
Metadata API | Deploy customizations between Salesforce environments | X | ||
Metadata Coverage Report | Determine supported metadata coverage across several channels | X | ||
Mulesoft Composer | Build process automation for data using clicks instead of code | X | ||
Outbound Messages | Send messages to external endpoints when field values are updated | X | ||
Platform Events | Send secure and scalable messages that contain near real-time event data | X | ||
Pub/Sub API | Subscribe to platform events, Change Data Capture, or Real-Time Event Monitoring | X | ||
PushTopic Events (Legacy)* | Send and receivedata change notifications matching a user-defined SOQL query | X | ||
Salesforce CLI | Develop and build automation when working with your Salesforce organization | X | ||
Salesforce Diagrams | Create diagrams to show business capabilities and technical details | X | ||
Salesforce Extensions for Visual Studio Code (Expanded) | Official VS Code extensions for Salesforce development | X | ||
Scratch Orgs | Deploy Salesforce code and metadata to a disposable org | X | ||
Second Generation Managed Packages | Develop and distribute apps for the AppExchange | X | ||
Tooling API | Build custom development tools or apps for Lightning Platform applications | X | ||
Unlocked Packages | Organize metadata, package an app, or extend an AppExchange app | X | ||
*Salesforce will continue to support PushTopic and Generic Events within current functional capabilities, but does not plan to make further investments in this technology. |
Resource | Description | Separation of Concerns | Interoperability | Packageability |
---|---|---|---|---|
A Primitive Look at Digital Integration | Develop a common language for connectivity concepts | X | ||
Applying Domain-Driven Design with Salesforce | Orient your solutions around business capabilities | X | ||
Best Practices for Second-Generation Packages | Understand 2GP packaging patterns and practices | X | ||
Components Available in Managed Packages | Understand managed package metadata components | X | ||
Design Standards Template | Create design standards for your organization | X | X | X |
Event-Driven Architecture Decision Guide | Compare event-driven architecture patterns and tools | X | ||
Events Anti-Patterns | Identify anti-patterns to avoid when using events | X | ||
How to design message-driven and event-driven APIs | Read up on the differences in a MuleSoft dev guide | X | X | |
Learn About the Jobs to be Done Framework | Explore JTBD on Trailhead | X | ||
Manage Global State in B2C Commerce | Easily pass data between components to maintain state | X | ||
Messaging Guidelines | Communicate relevant information and create moments of delight | X | ||
Messaging Types | Understand different messaging types by the nature of user interaction | X | ||
Metadata Types | Understand the different types of metadata in your Salesforce org | X | X | |
Migrating Changes Decision Guide | Choose the right deployment option for your solution | X | ||
Mobile App Notification Types | Understand notification types for Salesforce mobile apps | X | ||
Optimizing the View State | Maintain state in a Visualforce page | X | ||
Salesforce Developer Experience (DX) | Manage and develop apps on the Lightning Platform | X | X | X |
Unsupported Metadata Types | Identify components that aren’t available in Metadata API | X |
Help us keep Salesforce Well-Architected relevant to you; take our survey to provide feedback on this content and tell us what you’d like to see next.