DEV Community

onetomkenny
onetomkenny

Posted on

The Journey Of A Brownfield Monolith Towards SOLID

Our First Thousand Unit Tests...

"Wait, we’ve written 1000 Unit tests?"

This would have seemed like a ridiculous goal 18 months ago. Our 19 year old monolith platform created by (on average) four developers was the sort that everyone pretends not to work on these days:

  • 240,000 lines of C# code, with no unit testing (though we did have other forms of automated acceptance testing and js based unit tests on the front end).
  • C# code is written directly against volatile dependencies like database and caching.
  • Subject Matter Experts confidently stride into dark corners of the code whenever there is a problem.
  • The code is structured enough that non-SMEs can work when SMEs are on vacation even though its slow going. The code is “pretty” though decidedly legacy (as per Michael C Feathers).

Fourth Time's The Charm

Over the years we had made probably three half-hearted attempts at unit testing in the business layer but ran into the same problems that everybody else does:

  • How to get data into the methods you want to test You’ll try a special database first. You will grow old trying to keep it in the right state and tests will run glacially slow.
  • Very little of our code featured Abstract, Virtual or Interface based practices. That seemed to leave reflection based testing as an option. A very non-performant, brittle option that looked best suited to getting code into a test harness for a more aggressive refactoring that ended with more unit test coverage.
  • Our methods were "well written" but contain layers of nested logic. You can’t easily write a set of concise unit tests for that sort of code and end up confident that you are really testing every logical possibility.
  • And lets be honest, our ability to deploy new versions with massive internal changes that don’t cause huge fires proves that we are great developers right?

Still, you feel growing unease because the application is growing in complexity and it would be nice to hire new people without them having to spend six months learning all the system’s secret handshakes.

You know the world has moved on since you left school too. You could have written off Dependency Injection, Mocking, Unit Testing and Compositional coding approaches as “probably just fads” ten years ago.

Maybe…

But now this is all mainstream stuff with a proven track record and the best developer interview candidates get quiet when you let on that you don’t really do any of that SOLID stuff at your company.

And There Was Another Problem

Our business logic was tightly coupled to a number of things that were slipping into legacy status. Without seams in the code our business and repository logic was mixed up with the implementation details of those components. Some of them (like Full Framework) were on the list of “Definitely someday we need to deal with that”.

But how could we really make progress against something so large when the platform has to add features just to pay the bills today? And what about all the other dependencies in the meantime? The thought of a business layer containing nothing but business logic indifferent to the outside world seemed like such a beautiful utopia.

Growing feelings of long term dread coupled with a change in company ownership created an opportunity to lead an attack on all of these through an initiative creatively called “Do Unit Testing, But For Real This Time”.

There was no shortage of good vibes coming from the newer team members, but the rest of us knew how past attempts had gone. We knew it was going to take more than getting everyone to just say they would do things better.

The Slow Start...

The most obvious pain point was getting data into the methods we wanted to test. We already knew we needed to use mocking rather than trying to maintain a slow database. The major mocking tools and the blogs about Test Driven Development were all talking about Dependency Injection. There was no shortage of punchy little demos proving just how easy it all was.

But it wasn’t easy for us because the state of our existing code was nothing like those examples.

Another problem was that every DI example was introducing considerable amounts of abstraction into examples that started out as some pretty straightforward coding. It seemed like making the system easier to manage meant you had to make it way more complicated. Could this be sold to a team already used to doing things “the obvious way”?

What about the risks of this bold new direction? It didn’t seem like you could just break off a little piece from these techniques and then add more later on if it went well. You had to do them all to write testable code and you had to wrap your head around several new disciplines and integrate them all to have a shot at ending up with something you wanted to work on every day.

Who do you trust for good advice? Can you really bet your architecture on a blog post or a few witty remarks in a stack-overflow comment?

Inspiration Comes…

Then I heard a .NET Rocks interview with Steven van Deursen about the tools and practices required to approach Dependency Injection effectively. This interview was what I had been looking for. Finally, I was hearing a coherent vision for both the “why” and the “how”. It made sense. And it seemed realistic.

Steven wrote a book with Mark Seeman full of the knowledge that comes out of dealing with these issues repeatedly in many organizations in several languages over decades. The book is quite abstract, but a few reads gave me the confidence to formulate a plan for an “all-in” approach to updating our architecture to something fully modern.

What really won me over was Steven’s desire to create tools and approaches that “push the developer to fall into the pit of success”, rather than just demo a bunch of neat ways to do things.

This blog will be about my experiences, approaches, mistakes and realizations introducing SOLID principles as a main feature of the platform I work on daily.

I’m ready to write it now because with about 45% of our domain/repository code now based on DI Services and with over 1000 fast running, maintainable, concise, resilient unit tests written (and no signs of slowing down) we know that what we are doing is working.

We are slowly climbing the mountain. And we are still adding features. And we are getting better at what we do as a team and as individual developers and this will help our development team attract more fellow travelers.

And honestly, there is a bit of humble bragging here too:

Converting a large monolith to use Dependency Injection in support of Unit Testing is not easy. I was lucky to be among friends when I took on the task of reworking the entire business and data access layers of our platform and setting coding standards for others to follow.

Our informal deal was something like, "Everyone makes a good faith effort to follow my lead and I am honest to the point of total humility about what's working and improving what's not working."

If you are feeling like we were a little while ago, then join us for the journey.

The first post is here.

Top comments (0)