Have you ever been on a project or in charge of one where the developers are frightened to make even simple changes to the source code? These applications often grow from relatively simple applications into complex beasts where every change is like a game of Jenga where the entire tower can be brought down in a single move. They have evolved into complex spaghetti code of events being emitted and subscribers firing off multiple requests for the same data. How did we get here and how do we unwind the mess so we can get a firm hold on what is going on?
I think it’s best to understand where we came from..
These large controller/large views spurred a new revolution; break down your large controllers/views into small components. No longer was a web page one big component, but rather a collection of components collaborating together to create a cohesive page. Components became reusable and could be shared across multiple pages. They communicated with each other by emitting and listening for events. They could load their own data as needed and be self contained.
Breaking down the large pages into reusable components revolutionized web development, but came with a new cost: more complexity. Developers would now be responsible for following and understanding the flow of events, which inevitably would lead to a spaghetti mess of events firing; often where each component was deciding how to react. Web applications could quickly turn into a complex series of events, impossible to track, that would duplicate server calls and potentially create race conditions. These complexities make even basic maintenance a nightmare and feature refactors almost impossible.
So how do we rid ourselves from a mess of eventing spaghetti that keeps us up at night as we imagine every possible thing that can and will go wrong if we touch it? There has to be a better way to manage complexity.
In 2015, Dan Abramov and Andrew Clark created a framework called Redux and was inspired by Facebook’s Flux framework. This framework helps to reduce complexity by managing the constant change of state in web applications. It gives us a consistent and reliable pattern for how to develop our applications with a clear separation of concerns. In April 2017, Rob Wormald announced at Ng-Conf the release of NgRx, a Redux implementation for Angular using RxJs.
We started adopting NgRx as soon as we got back from Ng-Conf 2017 and we loved what we saw. We now had a way to clearly define each user and system event through actions. We had a consistent way of updating our centralized store of data through reducers. All of our asynchronous calls were easily defined in effect classes and we no longer had components knowing anything about services or how data was managed. The recommended container vs presentation components helped us to maximize the reuse of every component. We learned a lot of lessons in those first few months and have spent the last year training the community on best practices for complex applications and NgRx solutions.
We have a defined and easily repeatable process for fixing complex applications. I’ve outlined the steps here to explain it in more detail:
Step 1 - Address the Big Issues
If the application development is already started (or complete) and the complexity is becoming overwhelming, the first step is to identify the biggest pain points that are creating the biggest issues. Often we can quickly identify and fix glaring issues to bring some relief to the project. We document each of the issues, including the timeline and outcome so the management team can triage and prioritize which issues to address first.
Step 2 - Identify Features
Once we have addressed the worst offending issues, it’s time to start planning how we are going to reduce complexity. During Step 1, we identify which pages are good candidates for NgRx, rank them by complexity, and create an epic to track the process. We then create a story for each domain entity and tasks for creating classes for actions, reducers, effects, and selectors. Once we believe we have the stories sufficiently vetted, we begin to estimate each story by assigning points using a standard fibonacci sequence. We continue to break the stories down until each one can be completed in a week or less. Once everything is estimated, we understand how long it will take to refactor the application and we assemble the rest of the team.
Step 3 - Process Planning
Complex projects comes with a lot of existing processes around infrastructure and/or development. It’s important to take some time to talk about where this application is going to be deployed, branching strategies, continuous integration, coding style guides, code linting, automated tests, continuous deployment, source code management, etc. This step usually takes the least amount of time, but decisions here can greatly impact the success of your project. A little bit of planning can go a long way in making sure things go smoothly.
Step 4 - Lift and Shift
This step is where the coding happens. Our goal is to lift and shift each page/feature from the complex code into simple and reusable components. Using our custom schematics, we are able to quickly generate the redux patterns necessary for each feature. We are able to quickly jump in to define models, create actions, implement reducers, and code up our async operations into effects. We break down each feature during Step 2 into smaller and smaller pieces and now we get to create reusable presentation components that display data and emit events. This makes testing easier and maximizes components reusability. The top level containers handle system and user events while managing data from the store. We create automated tests for everything we build so we can reliably validate each one.
Step 5 - Quality Assurance
Our final step is an ongoing process that occurs at the same time we are performing Step 4. As each feature is developed it is peer reviewed before merged, it is checked for linting and style, tests are run, test code coverage is checked, and builds are verified. This stringent process is defined during Step 3 and ensures that all code works and follows best practices before it reaches a Quality Assurance (QA) engineer. Each story and epic must be approved by QA before it is considered complete. Once every story has been verified by QA, the application is complete.
Complex applications can keep us all up at night as they are very difficult to maintain and one wrong move can result in a disaster. If you haven’t looked at a NgRx yet, it’s probably a good time to check it out. I would be happy to give you a demo of what is possible. Complex applications aren’t going to fix themselves, nor are they going to be fixed with the same thought process that created them. Getting a good night of sleep is long past due.
Who Am I?