What are solid design principles
We are going to go over what solid principles are and why they’re so important when you are designing your code even in the world of Salesforce. Obviously, this article is going to be focused on Salesforce and how to apply these principles, but they are applicable to all software design.
The SOLID design principles are a subset of the many design principles that exist in the world of software engineering put together by Robert C Martin or Uncle Bob.
They’ve been widely accepted these days as some exceptional principles when used together and used correctly. They are excellent when you figure out how to implement them.
Why are they so important? I guess that many of you have been in similar situations as mine, such as joining a new team or a new project and being tasked to update a code where maybe some business logic had changed and you needed to alter that code and you thought it was easy.
I mean, at first glance, it seems to be an easy task but then, when you get to it you realize it’s unreasonably challenging and it’s because the code is probably written in a way that is very hard to understand, in a way that makes it very difficult for you to change any part of it, for example: a method is doing different things, or a class is responsible for different areas of the system where it shouldn’t. It just becomes unbelievable complicated to change something that’s so small. It should take you just some minutes or a couple of hours depending on the complexity, but instead, it ends up taking you days.
The goal of the solid principles is to pretty much eradicate that problem, to be able to architect and create code that allows or tolerates changes well and makes it easy for the next developer to understand what you want or you don’t want to do on the code. Furthermore, suppose some time goes by and you must make some changes to your own code. If you can’t understand your own code after writing it, then it’s almost as if you haven’ written it because first, you’ll have to research to understand it and, after that, make the changes and all those things take time. So that’s the goal: to create code with design code that tolerates change and that is easy for you and other people to understand in the future.
What each letter in solid stands for
S-O-L-I-D stands for different design principles in software engineering:
S: Single Responsibility (SRP)
O: Open Close (OCP)
L: Liskov Substitution (LSP)
I: Interface Segregation (ISP)
D: Dependency Inversion (DIP)
Single resposibility principle
“A module should have one, and only one reason to change”
The first design principle among the solid design principles is the SPR. In my opinion, this is maybe the principle people get most confused about. It’s a little harder to understand at a core level because the single responsibility principle seems easy, but there are so many definitions of what it should be and many of them are not exactly correct. So, there is a lot of misinformation about it, which leads to it not being implemented the way it probably should. The historical definition, the one that you’ll see most commonly on the web or wherever is that a module should have one and only one reason for change but the question is what is the core driving factor behind that reason for change?
I guess the best definition is the one by Robert C. Martin in his clean architecture book, where he states that a module should be responsible to one and only one actor in your system.
Suppose you need an “Account Calculator” functionality in your system, which you should use in different ways for your “Chief Financial Officer” (CFO) and for your “Sales agent” (SA). So, you need to encapsulate those classes in those methods so that they only change based on the actors (group of users). Now supposes that at some time in the future those use cases change and is required a change for “Sales agent”. It needs to be calculated in a different way, but the “CFO” doesn’t want to calculate it that way because it just destroys their view of things and is not relevant for them. If you don’t have the classes as an individual in your system, when you go and change it for the “SA” then, it is going to change it for the “CFO” also. You can see an example of that in figure 1, which is a wrong design, a broken SRP principle, and a good design in figure 2.
Another recurring question, at least for me in my beginnings, is how to put all this stuff into code? Well, I will show you figure 2 on the code.
You can check this from “Debug – > Open Execute Anonymous Windows” and try your call implementation with controller and you should see something like Figure 3 log:
So first let’s just create a lightning web component (LWC). I’m going to call this SRPjsExample.
Open closed principle
“A software artifact should be open for extension but closed for modification”
Open closed principle states that a software artifact should be open for extension but closed for modification, it’s open for adding some new functionality without modifying our already existing code components.
I will continue with the “Account Calculator” example. Suppose I have rendered my account data in the UI but now I want to present that account data in a different way, for example on a different component to present it as a fancier report as opposed to the previous one.
The question is: can you do that without having to rewrite a whole bunch of your code? If you design your code with the OCP in mind all you’d need to do is to build that new UI and you wouldn’t have to go back and change the logic that calculates the information for those bar graphs in the other one. You’d just change the UI and if you modify virtually nothing, just create your new UI component. Then have it pulled from those service classes or business logic classes into your new UI. Perfect! You have implemented the OCP correctly.
Use case: The marketing department requires each contact to be able to display the location of the other contacts in the system. So, in order to do so, you just create LWC map component and apex controller for serve back-end data to component:
This’s worked so well that comments arrive at the finance department and days later the finance director asks you to implement the same map but now for the accounts. So maybe your first thought is: that’s simple!, I’ve already done that, I just need to add some logic to my apex class for filtering the cases (“Contact, “Account”) to query the correct object but… you can’t do that! I mean, you can do it, but in doing so, you’d break the principle. So, the only way is to refract your apex back-end. One way is to design your class somehow like showed in Figure 4. In you LWC js file you just need to call to MapController, and don’t need to do anything else. Your component will take care of the rest when inserted on the Account and Contact page.
You can check the implementation of figure 4 in:
Liskov substitution principle
“If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.”
Substitution principle is the most confusing principle in terms of name and definition, but it’s probably the least confusing in terms of what is expected to achieve. Robert C. Martin summarizes it: Subtypes must be substitutable for their base types.
If you have a sub-class that extends a super-class, or you have a class that implements an interface, that class which is implementing the interface or extending the super-class, it needs to be able to use all the methods within that interface or within that super-class without things breaking. If you are going to extend every method in that super-class, it needs to be usable and cannot be something that causes your sub-class to break.
I will try to explain this rule in other words with overly simplistic example. Suppose you have a super-class of animal with a couple of sub-classes that extend animal. We have a “dog” class that extends animal and a “tiger” class that extends animal, and one of the methods within animal is “roar”. Well, dogs don’t roar, they bark, and tigers roar. Now the thing is, if the dog extended the animal class and tried to implement the “roar” method, you would get to throw an exception.
In this case, we tried to calculate all related products and orders to a companyId. So, in order to do so, we created an interface ITotalRelatedCalculator. Perhaps you could already realize that our future inconveniences have already begun here, but it doesn’t matter, we’ll continue. We now create two classes ProductCalculateService and OrderCalculateService for implementing our interface. If we check our ProductCalculateService class example, you can see we try to calculate calculateTotalRelatedOrders inside ProductCalculateService. Also, we do similar things for OrderCalculateService. If you go to “Debug – > Open Execute Anonymous Windows” and execute CalculateService.calculateTotals(‘Product’) you should see figure 5 output.
We got an exception so we need to fix that. So you could go to CalculateService class and change this:
We implement the correct case on LSP_Example/good folder. If you check ITotalRelatedCalculatorGood just have one thing and OrderCalculateServiceGood/ProductCalculateServiceGood implement the method independently for each case: https://github.com/cristiaan3003/salesforce-solid/tree/master/force-app/main/default/classes/LSP_Example/good
If you want to test it, please go to “Debug – > Open Execute Anonymous Windows” and execute
Interfaz segregation principle
“Clients should not be forced to depend upon interfaces that they don’t use”
That has multiple definitions and causes a little bit of confusion about what the actual point of it is. The most common definition is that clients should not be forced to depend upon interfaces they don’t use.
Suppose you have a class Z (i.e: AllMethidInOneBigClass), and this class has several functions in it. Also, you have class A, B and C and they all call this class Z. It has a bunch of functions, so now you have these three classes all depending on big Z class. Also suppose class A only needs method one in Z class, B needs method two and three, and class C only needs method four.
It doesn’t need to depend on it, I mean, it does need to depend on the methods in that class, but it doesn’t need to depend on the entire class. So, you could use interfaces to segregate those dependencies. For example, for class A, you could create an interface containing only the methods related to it, in this case, only the first method within class Z. This way, class A can be independent from the defined method in Z. We should do the same for classes B and C to segregate your dependencies on that gigantic class using interfaces.
There are other ways, but that is the general idea. You must make sure that your code only depends on what it depends on. This is important for a lot of reasons, but the primary reason is, if you have class A, B or C depending on a bunch of stuff they don’t need to depend on, then class A could break for reasons that don’t even make sense.
You can see an implementation on: https://github.com/cristiaan3003/salesforce-solid/tree/master/force-app/main/default/classes/ISP_Example/wrong
“Debug – > Open Execute Anonymous Windows” and execute
RelatedToA calcA = new RelatedToA();
Now suppose that, for some reason, another developer modifies getUsers method on ProcessUsers class for example with
If you go back to “Debug – > Open Execute Anonymous Windows” and try to execute again the previous code you will get something like this:
That thing affects the entire class, the entire method. That is not good because all parts of the system related to this class are affected.
Well, anyway, in figure 8, I show you a good design for this case. You can see that we have divided class Z into several interfaces that could be said to take and isolate each particular functionality in services A, B, C. So now the method ProcessUsers only is imported by the service that actually uses it and a change to it will not affect the other services.
You can see an implementation on: https://github.com/cristiaan3003/salesforce-solid/tree/master/force-app/main/default/classes/ISP_Example/good
“Debug – > Open Execute Anonymous Windows” and execute
RelatedToA calcA = new RelatedToA();
Now suppose same previous case and developer modifies getUsers method on ProcessUsers class, tries to execute again two previous lines. You can see that it runs without issues this time and just gets an error when trying to execute
RelatedToC_ISP calcC = new RelatedToC_ISP();
And that is wonderful because it doesn’t affect the entire class, it just takes down methods related to CService class.
Dependency inversion principle
“Separate the details from the concrete high-level implementations”
The goal is to separate the details (UI, Batch classes, etc.) from the concrete high-level implementations (typically service classes/business logic). In other words, suppose you have your UI, if you wanted your UI to look different then you should be able to make it look different and still use the same business logic, you should be able to exchange it basically at will whenever you want it and you would have to write new code for the UI, but the business logic implementation shouldn’t have to change.
By way of illustration, the dependency inversion principle tells us that the most flexible systems are those in which source code dependencies refer to abstractions. It is impossible to get away from concrete class implementations like class uses system languages, but we can use those concrete implementations because the almost never change.
You should be aware that breaking the dependency inversion principle is almost always related to business logic classes. So, like we have in the Account calculator service, this is something that might change in some orgs because Account calculation might change depending on the profile in the system. If you had a concrete implementation of Account calculator, to add a new functionality to a new profile, you’ll probably need to modify your business logic. That is precisely what this principle tries to avoid.
The objective is to try to build classes for business logic as abstract as possible to avoid rebuilding them. The only thing is how do that? Well, we have at least a couples of tools for that, but one of them is called dependency injection (DI) and another factory pattern (FP).
I’m not going to get into details here about the differences between them, because the content would really be for another post. I’d just say FP is used to create objects. We have separate Factory class which contains creation logic and DI, responsible for creation and injection of the object. Probably most of one have realized this, but we’d already seen a Factory solution way on the SPR example, so here I’ll show you the same Account (first example for SRP) problem but with a DI solution.
In order to try this, you can go to “Debug – > Open Execute Anonymous Windows” and execute
AccountCalculator_DIP accCalcDIP = new AccountCalculator_DIP(new AccountCalculartoService_CFO_DIP());
I think learning and implementing SOLID design principles is important because it increases maintainability, scalability, reusability and testability of the code. These principles are global and apply everywhere. SOLID might seem to be a handful at first, but with continuous usage you’ll see your code can easily be extended, modified, tested, and refactored without any problems. I believe these are 5 essential principles that must be used by software engineers around the world. If you are serious about creating robust and ‘SOLID’ software, and you haven’t used them yet, you must start applying these principles.