Content last updated July 2022.
Read about our update schedules here.

Introduction

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 simpler.

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:

This is a diagram showing how the topics in composable relate through three nested cards. The innermost card is a functional unit, the next layer is interoperability, and the outer layer is packagability.

A composable system:

This is a diagram showing how the topics in composable relate through three nested cards. The innermost card is a functional unit, the next layer is interoperability, and the outer layer is packagability.

Separation of Concerns

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.

Orient to Business Capability

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.

Manage State

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.

Separation of Concerns Patterns and Anti-Patterns

The following table outlines patterns to look for (or build) in your org and anti-patterns to avoid or target for remediation.

Patterns Anti-Patterns
Orient to Business Function 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
Manage State 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

Interoperability

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.

Messaging and Eventing

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 manage state 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.

API Management

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:

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.

Interoperability Patterns and Anti-Patterns

The following table outlines patterns to look for (or build) in your org and anti-patterns to avoid or target for remediation.

Patterns Anti-Patterns
Messaging and Eventing In your design standards:
- Clear standards exist for when to use synchronous patterns (messaging) andasynchronous 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

Packageability

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.

Loose Coupling

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.

Dependency Management

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 simple and effectively oriented to business capabilities requires a trade-off 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.

This is a diagram showing two packages, A and B, built to have no shared dependencies (vertical slice), built to have many common services (horizontal) and a mix of each (hybrid).

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].

Packageability Patterns and Anti-Patterns

The following table outlines patterns to look for (or build) in your org and anti-patterns to avoid or target for remediation.

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 Lsfdx-project.jsonL 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

Tools Relevant to Composable

ToolDescriptionSeparation of ConcernsInteroperabilityPackageability
Apex REST Web ServicesExpose your Apex classes and methods to external applications via SOAPX
Apex SOAP Web ServicesExpose your Apex classes and methods to external applications via RESTX
Change Data CapturePublish changes to Salesforce recordsX
Custom Metadata TypesDefine reusable, customizable, packageable functionalityX
DecoratorsExpose functions or properties publically as an apiXX
Dev HubManage scratch orgs, second-generation packages, and Einstein features.XXX
Generic EventsSend custom events that are not tied to Salesforce data changesX
Lightning Data ServiceCache and share data across componentsXX
Metadata APIDeploy customizations between Salesforce environmentsX
Metadata Coverage ReportDetermine supported metadata coverage across several channels X
Mulesoft ComposerBuild process automation for data using clicks instead of codeX
Outbound MessagesSend messages to external endpoints when field values are updatedX
Platform Events Send secure and scalable messages that contain near real-time event dataX
Pub/Sub APISubscribe to platform events, CDC or Real-Time Event MonitoringX
PushTopic EventsSend and receivedata change notifications matching a user-defined SOQL queryX
Salesforce CLIDevelop and build automation when working with your Salesforce organizationX
Salesforce DiagramsCreate diagrams to show business capabilities and technical detailsX
Salesforce Extensions for Visual Studio Code (Expanded)Official VS Code extensions for Salesforce developmentX
Scratch OrgsDeploy Salesforce code and metadata to a disposable orgX
Second Generation Managed PackagesDevelop and distribute apps for the AppExchangeX
Streaming APIStream events using push technologyX
Tooling APIBuild custom development tools or apps for Lightning Platform applicationsX
Unlocked PackagesOrganize metadata, package an app, or extend an AppExchange appX

Resources Relevant to Composable

ResourceDescriptionSeparation of ConcernsInteroperabilityPackageability
A Primitive Look at Digital IntegrationDevelop a common language for connectivity conceptsX
Applying Domain-Driven Design with SalesforceOrient your solutions around business capabilitiesX
Best Practices for Second-Generation PackagesUnderstand 2GP packaging patterns and practicesX
Components Available in Managed PackagesUnderstand managed package metadata componentsX
Design Standards TemplateCreate design standards for your organizationXXX
Event-Driven Architecture Decision GuideCompare event-driven architecture patterns and toolsX
Events Anti-PatternsIdentify anti-patterns to avoid when using eventsX
How to design message-driven and event-driven APIsRead up on the differences in a MuleSoft dev guideXX
Learn About the Jobs to be Done FrameworkExplore JTBD on TrailheadX
Manage Global State in B2C CommerceEasily pass data between components to maintain stateX
Messaging GuidelinesCommunicate relevant information and create moments of delightX
Messaging TypesUnderstand different messaging types by the nature of user interactionX
Metadata TypesUnderstand the different types of metadata in your Salesforce orgXX
Migrating Changes Decision GuideChoose the right deployment option for your solutionX
Mobile App Notification TypesUnderstand notification types for Salesforce mobile appsX
Optimizing the View StateMaintain state in a Visualforce pageX
Salesforce Developer Experience (DX)Manage and develop apps on the Lightning PlatformXXX
Unsupported Metadata TypesIdentify components that aren’t available in Metadata APIX

Tell us what you think

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.