- A Modular Approach
A Modular Approach
Many software projects suffer from a lack of initial design, while this approach allows developers to quickly create and release their software, it often creates a post initial release nightmare when it comes to updating, extending or fixing bugs. There are many reasons for throwing things together quickly, primarily I would argue that market and financial pressure are chief amongst them. However, there comes a time when the cost of updating or enhancing a product far outweighs the lack of initial design.
There are several principles in good software design-architecture which are critical to the success and longevity of a project, chief amongst these is the following:
- N-Tier: A client server architecture in which presentation, application processing, and data management functions are physically separated into layers.
- SOLID: Single responsibility, open-closed principle, Liskov substitution principle, interface segregation and dependency inversion principle.
- DRY: Don't repeat yourself. Software development principle aimed at preventing repetition of code.
- IoC: Inversion of control is used to increase modularity and extensibility through dependency injection.
When software is badly designed some of the principles above are missing entirely, or in other cases all tier's are housed within the same executable or assembly. This is where things begin to fall down and maintenance becomes more of a headache and ultimately more expensive in the long run.
The problem is magnified when an entire product base is copied and pasted to form the basis of a new solution for a new client. Whilst this gives an initial leap in development time, it also means that the maintenance of the products base is doubled. Fixing a bug in one product ultimately means you have to copy and paste the fix to each derivative solution.
Whilst different client solutions can diversify and become more generic to the client, there are still many aspects which are jointly owned. For instance the loading of application settings will not generally differ, however the settings being loaded could differ. Likewise storing application behaviour data such as debug information or process flow will chiefly remain the same. The elements that may differ are when it comes to displaying specific product information or a user's account could have different account options.
If we look at a single website built using C# with the model-view-controller (MVC) pattern there will typically be many modules that make up the software, for instance you could have:
- Shopping Cart
- Stock Control
- User account
Each of these modules makes up the first tier or layer; this is effectively known as the Presentation Layer (PL).
The next layer in the system is the Business Object Layer (BOL), within this layer we provide the business logic and interface with the data access layer to enable us to present the data. Each module within the PL has a corresponding module within the BOL.
The next layer is known as the Data Access Layer (DAL), this layer interacts via the BOL to ensure all data (CRUD) operations take place in a reliable way. Again, each BOL module has a corresponding DAL module.
Based on the above example a basic website would have 7 modules within each layer. Each module has responsibility for a specific area and could easily span multiple projects. If we take a single module and share it between multiple projects we have the benefit of reducing the code base, the cost of development and ultimately increasing the maintainability of the product.
If or when it is time to give our website a makeover we only have to focus on a single layer within the system, this being the PL. Likewise if a client has specific needs to use a specific data store then only the DAL would need to change.
Plugin technology is a natural proponent to the above principles, it does not replace any of them, instead it enhances them and allows developers to extend websites using SOLID and DRY principles. Each module within system becomes a plugin. The plugin manager is responsible for loading the modules and ensuring they are registered within the MVC architecture. Once registered, the module would behave like an internally housed MVC component.
Net Core Plugin manager also provides its own interfaces which allow the host application to query plugin modules, this allows for a joined up approach when the system fit's together as a final solution.
A further advantage of using plugins is that each plugin becomes self-registering, when a plugin is loaded it can register it's capabilities and interfaces within the IoC container, this further removes essential tasks from the developer, freeing them up to concentrate on creating enhancements or fixing bugs.
Rewriting an entire application for a new architecture is not always a good approach, there are quite a few case studies and articles on why it could be a bad idea. Chief amongst these arguments would be that you already have a code base that is widely used, tested and bug fixed. This however should not prevent you from refactoring your existing code base to make it modular, enforce the use of the SOLID and DRY principles and begin a journey into true modular design that could then benefit from plugin technology.
It is never too late to expand a little time and energy on refactoring your existing code, in many cases it involves nothing more than decoupling different modules from the main application and housing them within their own assemblies. In the first instance these can be statically linked to the application with the benefit that you can begin to ensure your application is more modular, more extensible and fits within the aforementioned development principles.
The benefits of refactoring existing code into a modular design has to be a good thing, too often an application is so tightly coupled and intertwined that maintenance becomes an issue. A good example of this tight coupling is where IoC is advocated and used when creating object instances using a DI container, however the implementation is flawed when both the interface and interface implementations are housed within the same module. The disadvantage of this approach being that the intended loose coupling is now tightly coupled within the application.
Replacing the interface implementation with another implementation is nye on impossible without further tightly coupling the application, and preventing any type of code reuse. In this circumstance the interfaces should reside within their own assembly and the implementation should reside within another assembly.
The benefits of decoupling an application are many, in computing new technologies are continually being developed, whilst upgrade paths may not always be easy, if you have a well-designed, modular application, you can easily rewrite aspects to use a different technology without having to sacrifice the entire existing code base.
There is also a huge downside when developing non modular applications, if an application is too tightly coupled and relies on a specific technology, as the technology progresses it becomes more and more difficult to upgrade. The decision to upgrade is postponed as the amount of development and testing time increases, market and business pressures come into effect and the once dazzling application suddenly has a limited life span and becomes outdated rather quickly.
In a continually evolving sector, developers will inevitably upgrade their own skill base. Knowledge of older technology can eventually be lost over time resulting in a dwindling number of people with the required knowledge and expertise to maintain the older technology. Employers will find it more difficult to attract developers in order to maintain their application and the cost of development will rise exponentially.
With a well-designed modular application the task of upgrading a single layer of the application is no more difficult than replacing a single module or layer, much of the existing code can be reused and reutilised.