From what is described, it seems the overall system does not have well-defined boundaries, probably, not quite well designed. If possible, take a step back, reflect, redraw boundaries (I suppose more than one team is developing the system, so representatives from each one need to be present, also ops, if separate).
The general idea is to establish minimalistic APIs between the components. You probably already do have a good understanding where changes happen most often, so you know approximately relative stability of the components. Good practice here is to make unstable components to depend on stable ones and inverse dependencies with all that nice design patterns.
Problematic / legacy / fast-changing systems may need to be wrapped in what domain-driven design (DDD) calls Anti-Corruption Layer. Add that to places, which cause most of the pain. Actually, elements of DDD can help you to tackle exactly your problem, even if you do not follow it's philosophy to the full extent. You may start eg with understanding bounded contexts and different ways to their integration, see eg https://www.culttt.com/2014/11/26/strategies-integrating-bounded-contexts/ . The main idea is to make the system less complex, better aligned with business needs. Tactical patterns of DDD is secondary matter.
Another useful approach is to keep APIs (you data contracts) versioned and keep a graph of component version compatibility. (something similar to any packaging system, like Debian's). This needs some discipline, eg, being part of definition of done for each component release. I know, both Java and Elixir have excellent systems to handle dependent packages. Tools? It's up to you. Personally I find semantic web ones: RDF/OWL and inferencing engine to be up to that task, declarative, easy to setup and maintain, though with a need to learn some graph database principles and query language. Of course, there is a need to keep stage/production states to be able to deduce compatibility of the result, so it's better to be connected to some kind of Continuous Delivery system (hard to say where you are now on scale of CD, but automating is the key).
Plus, that packaging/release information better have a "master data" in the form deployment orchestration/choreography system understands, not in MS word. Certainly, cross-platform tools can be found for that (maybe, there will answers with tool recommendations). And storing system versions graph can well be in git. Probably, you will need to use release versions instead of git revisions to simplify maintenance.
As for middleware, Elixir allows as far as I understand hot-swapping code, meaning, your middleware does not have to stop even to handle switchover. In combination with versioning, it means that in many cases it is possible to use several versions simultaneously (you middleware is not tied to rigid database schema, is it?)
Those data contracts, if you devise some declarative way to describe them ala Swagger, can be the basis to documentation, testing, and even automatic code generation for adapters, which you may want on the borders of components.
This answer does not pretend to be too specific or all-encompassing. Try to think out of the box: You are the best expert of your system.