Recently, at work, I ran into an issue of updating a feature to a new UI. It happened that it came together with the data model change so it was the change across the stack. The first thought is that it would be easier to just start over leaving the old world untouched and just focus on a new shiny implementation that will solve all the problems the old one had. As we all know, when you think it is a mess and you will need to start over, very likely under the release pressure you will produce a new mess and you will end up in a worse position than you started. Fortunately, our app is nicely divided into layers and we are very good at testing both UI and unit so I can safely change the code without the regression risk.
I decided to not go with the temptation and refactor instead of rewrite. Because of the cleanish architectural structure, I was able to refactor layer by layer relying on the interfaces in between.
Since the data contract with the API was still in flux, I started with my domain. I knew how it should look like and I knew what sorts of data I would expect so I just defined my domain models and use cases. I used the old contract to feed the domain with data and just refactored mappers to map to the new models. Mappers in-between domain and presentation needed a small update as well to support new domain models, but all pretty cosmetic. This way domain was defined and prepared to feed my presentation layer. It could have been merged to master without any feature flag as a refactor without any functional changes.
On top of that presentation and data could be built in parallel. I started with a presentation to give the API contract some time to stabilize. Because the new screen was different enough to not reuse the old one with tweaks I decided to create a new UI behind the feature flag. Relatively easy process of building the view and feeding it with the data coming from the use cases. Once the domain was merged this could be put into a PR and merged in without problems. It was working fine, just some data was not populated correctly since we were still using the old API contract, but it was safe to merge because we are behind the feature flag so we won’t hit the production yet, allowing the designers to review the UI.
The last final step was to use the brand new API contract. Simply enough, I updated the graphQl schema and simply started consuming it. Because my new domain models were supporting both old and new views I needed to merge updated data with the old one to have all necessary fields. With few @Depraceted annotations, I ended up with a nice solution that contains two data models that are mapped to one domain model that drives old and new view.
That is pretty much my recent story about how Dependency Inversion and Interface Segregation saved the day for me and how the Clean Architecture with good test coverage helped me to be more productive and confident about my own work.