Just want to share my experience as a freelance software developer over the last 5 years working with different teams on B2C web applications in the cloud.
Big drivers for too much code are
A nice example is DI (dependency injection) for improving testability. This is only true if you do not have technologies that mock or simulate dependencies, e.g. some http client. But in all web-frameworks these technologies exist, e.g. you could use MSW (Mocked-Service-Workers).
In rare cases DI makes sense to enable an easy replacement of deeper layers, e.g. replacing the mail provider, but only use DI to enable testing of layers of your application introduces complexity that does not help the customer.
For sure there are benefits from it, but using inheritance and defining too much classes introduce abstraction that needs to be understood by other developers. Why should I split non-reusable routines in several classes? You need to jump around and find stuff instead of understanding the underlying problem. Hiding the problem in many small parts results in more code and less clarity.
Having a repository layer and a controller layer plus domain interfaces is enough in most cases. Sometimes you need to add some domain algorithms that can be implemented with simple POCOs - that's it.
I more often see (mainly JAVA backend REST services) where almost all classes depend on something else in any layer. Dependencies have to follow rules as:
- The repositories return domain objects
- The controllers access repositories to return those objects => Controllers reference repositories and domain classes => Repositories only reference domain layer => Domain objects reference nothing
What problem needs more dependencies?
Sure it was enlightening 10 years ago to learn about DI and how to write unit tests, but unit tests are rare - not many units have that much logic that needs to be tested.
It is more important to know if a controller does the right thing than to have unit tests for every layer. Therefore some level of integration seems to be the right way.
There is nothing like a rock-solid definition of integration tests, there is only the idea that it is less effort and more meaningfulness if integrating stuff.
- Write a single test covering stuff that belongs together
- Mock stuff like Network dependencies
- Only tests with easy-to-understand titles improve the quality
The bigger the change the higher the risk. This is true on both levels:
- Merge changes into your main branch
- Deploy changes into production
=> Every single small change needs to be deployed to productionn instantly, else the risk to break the production system is rising
The question is: How can this be achieved?
You cannot manually check for every side-effect a small change in the code has => All quality gates must run automatically.
- Always review code changes and ask questions
- Deny changes that do not have tests
- Every fix has at least one new test that prevents the bug from happening again
- In addition to the unit- and integration tests you need a fully integrated environment you run automated user journeys on using Cypress, Playwright or something similar
Use a checklist on every Pull Request
- What could possibly go wrong?
- Is there any test that should be added on unit or integration level
To be sure that your production system is up and running, add the most important user journeys as Cypress or Playwright tests and execute them avery 10 minutes against your production.
You will be able to detect errors that made it through all quality gates earlier than your customers. When the phone rings you can smile, tell them that the issue is already fixed and apologize for any inconvinience.
At least on a daily base. If something is red, discuss in the team immediatly the needed fix, do never accept recurring red flags, your sense will deaden.
If a developer is aware that his changes will go to production and could jeopardize the whole product, then he will deliver high quality, he will not accept doubts if his code will run, he will be sure.
If some accident happened, find the reason and address it. There is always a good idea that can be implemented to avoid the accident in the future.
When I experienced it first I was enthusiastic about building new environments whenever I need them. In reality this one never was a point. Instead it turned out that infrastructure as code is just the best way to have a 100% accurate description about your environments. Thats it, not more and not less.