The year is 2012 and we have acronyms of acronyms of acronyms. The word SmartJs is itself an acronym, the first letter of which is the first word of an acronym, which is itself composed of acronyms.
This post aims to discuss SOLID principles and, in particular, demonstrate their usage in the SmartJs framework.
This post aims to discuss SOLID principles and, in particular, demonstrate their usage in the SmartJs framework.
Background of SOLID
Please take a look here for some background on SOLID principles. It is rather heavy reading so Wikipedia's article may be easier to digest. We have been studying SOLID at our work and it has had a profound influence on my ability to formalize how I think about software development.
S = SRP; Single Responsibility Principle
SmartJs contains many examples of The Single Responsibility Principle. Most obviously, AMD provides a language construct that encourages modular software design. Each module of SmartJs is designed to do one thing only. Indeed, during the organic construction of SmartJs, several modules were born when the code in a parent module began to do more than one thing.
But more than the modules themselves, the SRP was built right into the functionality at a fundamental level. The most clear example of this is the interface to the Server module. Although the interface exposes public methods that provide data persistence services, the function calls to Server are designed to begin the process only, and report back success or failure. They never return a model representing the result of persistence, that is consumed by the caller. In cases where a model is the result of a persistence operation, that model data always follows as a separately published application event.
The design and enforcement of rules like this took awhile to evolve. By adhering to SRP, this design pattern doesn't seem difficult to work with, but rather a joyful reminder that SRP can be very helpful when correctly applied. The application components must always remember that, for example in a list operation, the command to list will not be immediately followed by a list of results. It is only a command to start the process, and a separate e.g. topic.list event will be present in the system a short time later with the contents of the requested data. This allows an opportunity for all the components of the application to respond equally and independently to the presence of new data and events in the ecosystem.
But more than the modules themselves, the SRP was built right into the functionality at a fundamental level. The most clear example of this is the interface to the Server module. Although the interface exposes public methods that provide data persistence services, the function calls to Server are designed to begin the process only, and report back success or failure. They never return a model representing the result of persistence, that is consumed by the caller. In cases where a model is the result of a persistence operation, that model data always follows as a separately published application event.
The design and enforcement of rules like this took awhile to evolve. By adhering to SRP, this design pattern doesn't seem difficult to work with, but rather a joyful reminder that SRP can be very helpful when correctly applied. The application components must always remember that, for example in a list operation, the command to list will not be immediately followed by a list of results. It is only a command to start the process, and a separate e.g. topic.list event will be present in the system a short time later with the contents of the requested data. This allows an opportunity for all the components of the application to respond equally and independently to the presence of new data and events in the ecosystem.
O = OCP; Open Closed Principle
The Open Closed Principle can be hard to pull off, especially in a project that is evolving quickly. It can be hard to have the foresight to know where OCP will eventually be needed. Sometimes a switch statement is a good place to look for a place to implement OCP.
I think JavaScript makes OCP a lot easier than C# because there is never a type conversion that needs to be done. Any object and any number of parameters can be passed to a function in any order, so Extensibility is rather built in. It can be a fine line to walk.
The best example of OCP I can think of in SmartJs is the conduit from the client to the server. It is boiled down to one function to call from client to server, and one function to call from server to client. Everything goes through this narrow pipe, and the pipe itself does not require any maintenance when new functionality is added to the application.
Two modules use the Server functionality, dataContext and groupContext. These wrap the Server in convenient methods, extending the Server's usability without requiring modification. These two demonstrate OCP and ISP well, since new functionality could be added to the system without ever needing to change server.js. The data conduit is truly open for extension, and closed for modification, making it a breeze to add interesting new realtime functionality.
I think JavaScript makes OCP a lot easier than C# because there is never a type conversion that needs to be done. Any object and any number of parameters can be passed to a function in any order, so Extensibility is rather built in. It can be a fine line to walk.
The best example of OCP I can think of in SmartJs is the conduit from the client to the server. It is boiled down to one function to call from client to server, and one function to call from server to client. Everything goes through this narrow pipe, and the pipe itself does not require any maintenance when new functionality is added to the application.
Two modules use the Server functionality, dataContext and groupContext. These wrap the Server in convenient methods, extending the Server's usability without requiring modification. These two demonstrate OCP and ISP well, since new functionality could be added to the system without ever needing to change server.js. The data conduit is truly open for extension, and closed for modification, making it a breeze to add interesting new realtime functionality.
L = LSP; Liskov Substitution Principle
SmartJs demonstrates LSP when it passes around a Knockout observeableArray as if it was an array, to the Validation module. Validation pushes errorMessages onto it as if was an array, never knowing or caring that the array is bound to the UI.
I = ISP; Interface Segregation Principle
Similar to SRP, modules in SmartJs are designed to do one thing. They return only the minimum necessary interface that other modules will need.
When a model in the system needs to be used for two different purposes, SmartJs will use two different ViewModels, each with their own UI binding and public interface. This prevents having a catch all interface that is cluttered and violates SRP.
DataContext and groupContext modules also provide segregation of the Server interface. Their interfaces are specific to data and group kinds of functionality respectively. New group functionality could be added to the groupContext without needing to change the server's interface. This results in a system that is less brittle overall.
When a model in the system needs to be used for two different purposes, SmartJs will use two different ViewModels, each with their own UI binding and public interface. This prevents having a catch all interface that is cluttered and violates SRP.
DataContext and groupContext modules also provide segregation of the Server interface. Their interfaces are specific to data and group kinds of functionality respectively. New group functionality could be added to the groupContext without needing to change the server's interface. This results in a system that is less brittle overall.
D = DIP; Dependency Inversion Principle
The best for last. I really think this is my favorite. It is the reason that AMD is so wonderful. It is what separates MVVM from MVC.
AMD allows modules to have dependencies fed to them at run time. There is no composition root. There is no Controller to call for help. There is only the View and the ViewModel, their binding, and events in the system which encourage things to happen. When model properties are changed, the user interface automatically udpates through binding.
Modules never have to know or care how their dependencies are fulfilled. They call an interface and have an expected result. Therefore, DI makes it easy to perform Unit Testing, by allowing the building of stub objects that fulfill the dependency interfaces without doing any real work.
I have a good deal of background working in MVVM in XAML/WPF for Windows app development. One thing that makes this pattern possible in that world, is Unity Controller. MVVM without a DI container is a lost cause. MVVM with a DI container is a pure pleasure to develop.
Therefore, implementing MVVM in JavaScript is correctly done using AMD and RequireJs provides this ability flawlessly.
AMD allows modules to have dependencies fed to them at run time. There is no composition root. There is no Controller to call for help. There is only the View and the ViewModel, their binding, and events in the system which encourage things to happen. When model properties are changed, the user interface automatically udpates through binding.
Modules never have to know or care how their dependencies are fulfilled. They call an interface and have an expected result. Therefore, DI makes it easy to perform Unit Testing, by allowing the building of stub objects that fulfill the dependency interfaces without doing any real work.
I have a good deal of background working in MVVM in XAML/WPF for Windows app development. One thing that makes this pattern possible in that world, is Unity Controller. MVVM without a DI container is a lost cause. MVVM with a DI container is a pure pleasure to develop.
Therefore, implementing MVVM in JavaScript is correctly done using AMD and RequireJs provides this ability flawlessly.