You've run into an issue that is an ongoing careful balancing act in modern software development. The more you loosely couple your components, the less you can rely on performance optimizations that rely on more than one component at the same time.
For example, if you want to extract a report which links all your Customer
, Invoice
and PurchaseOrder
data; the most performant method would be to write a single query and let your database server optimize its query. This is something a db server tends to specialize in.
However, from a coding perspective, this immediately muddies your loosely coupled components, as there is no clear separation between the three domain entities you've defined.
From a code structuring perspective, you'd rather fetch the Customer
, Invoice
and PurchaseOrder
data separately, so that you can keep these components independent from one another. That's great for your code structure, but your query performance is going to suffer because of it.
I'm using repositories as the example here, but the same applies to your situation. You're stuck between the cleanly loosely coupled services, and a desires to optimize things by coupling your services.
Whether you value performance or code structure, is not something we can universally answer.
whilst I started out with good intentions of high cohesion and loose coupling as soon as the delivery pressure ramped up I began 'code sharing' across packages and have now got tight coupling across the application
There's a saying in my native language, which loosely translates as "the last stones seem to weigh the most".
That is the classic development story. This isn't even development specific, the same applies to most projects people tend to undertake, e.g. building a house, cleaning a house, or painting something.
In the beginning, they start with clean broad strokes, keeping things efficient and elegant. But at the end of the project, when the end is very much in sight and a minor hindrance is encountered, people are much more likely to sweep things under the rug and be done with them. No one wants to redo the foundation when they're already doing the final detail work.
Side fact: the house I live in is a monument to this kind of behavior. I didn't cause it, past owners did (I just rent it), but it very much amuses me as a software developer, and annoys me as the tenant.
This is human nature, and there are justifications for why we engage in this behavior - though you run the risk of regretting your decision down the line. Especially in software development, the unexpectedly long-lived nature of codebases tends to bite you back in the end, which is why good practice developers are so sensitive to bad practice.
The main takeaway here is that you can't realistically expect that you would plan for everything correctly in advance when you first laid the groundwork. Instead, you need to shift your expectation to adjusting the groundwork when the need for it being adjusted presents itself. This comes in many forms.
Clean coding practices help lighten the load when the time to adjust things comes.
But sometimes a late change requires a significant change to the codebase, and this is where you need a PO or team lead who values the clean code enough to dedicate the needed effort and time to it. More often than not, even with the cleanest coding practices, the latter is what fails and causes these kinds of issues, and as a developer you generally don't have the authority to override this. At best, you can hope to sway your PO/team lead/manager's mind and give you the needed time.