It seems to me your question is split into two parts. The first revolves around duplication of interfaces. The second on dependencies.
First, duplication of interfaces. You probably want to duplicate them in the case you are describing.
A good rule of thumb when breaking up a monolith is to keep your boundaries clearly defined. If you have two domains, it can be counter-intuitive to keep in mind that any given domain object needs to be modeled in a way which is unique to the domain.
For instance, if you have a customer interface, ICustomer, and you have two domains, Orders and Invoices, although both orders and invoices utilize something called a customer, it is often better to define the interface twice than it is to try to force a single interface (usually due to a misunderstanding of DRY). This is because a customer from the perspective of the Order domain is a very different beast than a customer from the Invoices domain. It might seem intuitive that both Orders and Invoices share the same ICustomer interface. But in truth, they probably do not. A customer in an Order domain is very different than a customer in an Invoice domain, and will change for very different reasons.
So if you want to split your project into domain-driven micro services, create libraries around each domain. Don't be so much concerned about how well you can re-use code (or other resources) across domains, but how easy it is for the code in one domain to be changed without breaking code in an unrelated domain.
Second, dependencies. This will mostly solve itself after you address the above issue. Keeping dependencies simple is a matter of keeping dependencies secluded to their proper domain, and then pushing the interfaces for more generalized dependencies down into your base common libraries.
Source control should not manage dependencies.
Git submodules are great for sharing library code as long as the parent and child repositories evolve together, and often enough that managing this relationship in source control makes sense.
In your case, a need arises to develop these independently. Extra care must be taken to ensure breaking changes are not introduced, especially if you are dealing with multiple distros and clients.
You haven't specified a technology stack, but the solution to this problem is dependency management. If this were .NET then NuGet is your go-to tool. For Java it would be something like Mavin (and there are others too). JavaScript has NPM, Ruby has RubyGems, etc.
Keep each library in their own repository, but break the parent-child source control mix. Each library needs to have a package release and a version number associated with it (related: Semantic Versioning). Each distro needs to specify exactly which version number of the top most library it needs. That library needs to specify which version it needs for the next library down.
Distro A
- Library Collection v1.0.3
Distro B
- Library Collection v1.2.0
A good package manager can resolve the dependency graph and install the correct versions of each library.
Source control and dependency management are very different beasts, and require different tools.
Best Answer
Libraries and dependencies are like persons and relatives: One is just an entity (something), the other is a relational entity.
I am a person. My niece is also a person. But to her, I'm a relative. You cannot simply be a relative by nature; you're always a relative of someone else.
Similarly, a code library becomes a dependency only when another project uses it, and then it's a dependency of that project and not of another. Even though a code library is invented specifically for other projects to use, it's not a dependency until this actually happens.