For the past five years I’ve been leading a small group of highly productive Java software engineers. Together, we maintained three medium to large sized enterprise applications. We developed many features, finished a number of big projects, and did huge refactorings. In addition, we lived through two product launches for the general public.
Two of the applications were well structured monoliths while one of them was a well structured modular monolith. Together they had a total of 150 independently deployed units.
Here are what worked best for us.
- Involving everyone in the decision making process
- Continuous Integration
- Maintainable integration tests
- Visibility into the CI/CD pipeline
- Module dependencies instead of repository coordinates
- Calendar Versioning
Involving everyone in the decision making process
We had weekly meetings with team members where we talked about everything that seems interesting to us. We discussed every architectural decision in those meetings.
I highly recommend Martin Fowler’s article on Continuous Integration if you are not familiar with the concept.
We didn’t protect development branches in any of our repositories. Everyone worked on their local branches and pushed to origin whenever they wanted. We never mandated merge requests. We ended up integrating our changes a few times a day which helped us eliminate a lot of bugs at early stages during development.
I suggest you follow these discussions on Twitter: @kief, @martinfowler
Furthermore, original author of git-flow recently updated his article adding a “Note of reflection“.
Maintainable integration tests
We started building two integration test suites at first. One using SoapUI and another using JUnit. After a while JUnit test suite proved to be much easier to maintain than SoapUI projects. SoapUI stores projects in a single file. Keeping and diffing that ever changing file in Git is hard. As a result, we kept our SoapUI test suites only for local testing and built our complete integration test suite with JUnit.
Visibility into the CI/CD pipeline
Jenkins doesn’t provide a decent UI/UX. Blue Ocean looks modern but it doesn’t work if you have a lot of pipelines. We had 600.
An email notification from Jenkins for an unstable job doesn’t provide enough motivation for developers to fix the underlying issue. On the other hand when a beautiful dashboard reflected on a big screen goes ugly because of an unstable job people will notice. We developed that dashboard, procured a big display and advertised the status of our CI/CD pipeline to everyone on the same floor with our team.
Module dependencies instead of repository coordinates
There were a handful of common modules for every application we developed. While our team leads were responsible for maintaining those common modules we didn’t hide source code of the common modules from other team members.
We didn’t hide common module artifacts behind repository coordinates either. Every application module added them as module dependencies. By doing so we made sure that every single change was instantly available to every developer.
Keeping track of version numbers for 150 artifacts is a cumbersome job. I hated maintaining our legacy versioning scheme based on SemVer. Our decision for switching to CalVer was well received by test and DevOps teams as well. Everyone was instantly happy and no one ever missed SemVer.
Bonus material: Monorepos
We put every independently deployed module into its own repository. As a result, there were 150 repositories that we maintained. If I were to take over those three applications today I would without hesitation combine all those repositories into three monorepos. For many scenarios pros of using monorepo far outweighs cons of it.