So your company is working to improve its development pipeline. There’s a big project to work on your DevOps processes and toolset, and maybe even changes to the way your development is being managed. However, if you’re a developer these changes could be the least of your problems.
Let’s face it, code isn’t always perfect. Long-running codebases tend to grow organically. So, how are you going to get things organized and under control to make your transition easier? This is an especially difficult challenge when the improvements include a move to Salesforce DX from standard Apex.
It’s easy to add features when all of your code resides inside a single codebase. Things get much trickier when your code is split into several DX packages. How do you properly separate your code? How does your code communicate across package boundaries?
Your best option is something that Object-Oriented Programmers have been using for many, many years; Design Patterns. You’ve probably heard of them before, and there’s a reason for that. Once you get the hang of them, they make programming easier. They change the way you approach solutions and simplify conversations between developers during planning and strategy sessions.
Design Patterns also help you structure your code and communicate between layers, or packages, in a much more efficient and manageable way. That communication is very important as you transition to DX because of the ever-growing need to follow Separation of Concerns as you adopt more, and more of a true DX strategy.
As your code splits into packages, you’ll find that without proper separation of concerns your team(s) will be crossing package boundaries more than you anticipated. This can cause more work in adding features to, and maintaining, your applications. This would not help your company’s improvement efforts at all.
The solution is to start thinking of the code behind your applications in terms of modules of specific functionality, or blocks of code that serve a very specific and focused purpose. As you approach your code in this fashion, you will likely hit the inevitable question, ‘How do I get these packages to share resources and talk to one another cleanly?’
That is where Design Patterns can help. You can start by isolating the functions of your code into their logical components. Meaning:
- Pulling all of your SOQL into sObject-specific Selectors.
- Pulling all business and execution logic OUT of your Triggers, and into sObject specific Domains and Services
- Pulling business logic and execution OUT of your Controllers, and into Services, Strategies, Providers, and more.
- Build Factories so that your components and packages can request instances of Selectors, Domains, Services, etc rather than tightly binding to one another.
- Creating Facade classes as the interface between your custom code and 3rd party managed packages or external APIs.
- Selecting an owning Package for each sObject & Field.
Whether accomplished through the use of third-party, open-source offerings such as the FFLIB and Force-DI suites or built in-house following the guidance of the Gang of 4, Martin Fowler, or Andrew Fawcett you can simplify your future code maintenance and your transition by learning and mastering Design Patterns.
The process of cleaning up, or refactoring portions of, your codebase to fit proven Design Patterns often leads teams toward identifying their modules, or packages. This effort also reveals dependencies between these modules and packages and helps to identify which package should own which metadata across your org.
For example, as you go through the process of building Selectors and moving SOQL statements into them you’ll be able to clearly identify all of the functional pieces of code across your org that need access to certain sObjects. However, some of that code might logically belong to different packages. Further, only one of those packages would own the original Selector for a given SObject.
Let’s say your company manages inventory, sales, and shipping all through Salesforce. Those three logical systems are all likely to utilize shared data. However, not all of them need the same data: Sales is likely to need data from the Inventory system; Shipping is likely to need some data from Inventory and some data from Sales; Inventory could use sales forecasts for placing orders for additional inventory and data from Shipping to reduce inventory available to be sold once product ships.
All of this points to a dependency chain where Sales would depend upon Inventory, and Shipping would depend upon both Sales and Inventory. Both Sales and Shipping should be able to extend or inject functionality on top of what Inventory is providing them. Inventory package would then be isolated in its code and focus, and not carry around the weight of Sales and Shipping custom objects, fields, and code.
Fields added to a sObject for the benefit of Sales should be owned by the Sales package. Fields added for the benefit of Shipping would be owned by the Shipping package. The motivation behind this is that you wouldn’t necessarily want the Shipping team’s developers crossing over into the Inventory and Sales packages to adjust things. That could lead to code collisions, and a much more difficult time maintaining the system over the long haul.
All of this layering, dependency management, and separation of concerns is easier with properly implemented Design Patterns. The end goal is to isolate your codebase into a state where it is easy to identify where changes and features belong, and easy for teams to work in isolation with less worry about code collisions and deployment issues. It may seem like more work to get your code to that point, and it likely is. The pay-off is substantial, though, and very much worth the effort.