I've been trying to get buy-in from colleagues to have stricter boundaries between modules but without much success, mainly because I don't fully understand how to do it myself.
Let's say we have 3 different modules, all domains: sales, work, and materials. A customer places an order, someone on the factory floor needs to process it, and they need materials to do it. Materials know what work they are for, and work knows what order it's for (there's probably a better way to do this. This is just an example).
On the frontend, users want to see all the materials for a specific order. You could have a single query in the materials module that joins tables across domains. Is that ok? I guess in this instance the materials module wouldn't be importing from other modules. It does have to know about sales though.
Here's another contrived example. We have a certain material and want to know all the orders that used this material. Since we want orders, it makes sense to me to add this in the sales module. Again, you can perform joins to get the answer, and again this doesn't necessarily involve importing from other modules. Conceptually, though, it just doesn't feel right.
It is a bit hard to just explain in a bunch of comments.
In your examples you need to add extra layers, just like you would do with the microservices.
There would be the DTOs that represent the actual data that gets across the models, the view models that package the data together as it makes sense for the views, the repository module that actually abstracts if the data is accessed via SQL, ORM, RPC or whatever.
You should look into something like:
"Domain-Driven Design: Tackling Complexity in the Heart of Software"
You need a thin layer on top that coordinates your many singular domains. We use graphql to do this. API Gateway or backend for frontend are similar concepts. Having many simple domains without any dependencies is great, they just need to be combined by a simple with many dependencies - a coordination layer.
Joining on the database layer is still adding a dependency between domains. The data models still need to come out of one domain. Dependencies add complexity. So joining is just like importing a module, but worse because it's hidden from the application.
If you really need a join or transaction, you need to think as if you had microservices. You'd need to denormalize data from one domain into another. Then the receiving domain can do whatever it wants.
Of course, you can always break these boundaries and add dependencies. But you end up with the complexity that comes with in, in the long run.
It seems like maybe your onion could use an additional layer or two.
If I understand your example, the usual solution is to separate your business objects from your business logic, and add a data access layer between them.
In terms of dependencies, you would want the data access layer module to depend on the business object modules. And your business logic modules would depend on both the data access layer and business object modules. You may find that it is ok to group business objects from multiple domains into a single module.
Note that this somewhat mirrors the structure you might expect to see in a microservices architecture.
Let's say we have 3 different modules, all domains: sales, work, and materials. A customer places an order, someone on the factory floor needs to process it, and they need materials to do it. Materials know what work they are for, and work knows what order it's for (there's probably a better way to do this. This is just an example).
On the frontend, users want to see all the materials for a specific order. You could have a single query in the materials module that joins tables across domains. Is that ok? I guess in this instance the materials module wouldn't be importing from other modules. It does have to know about sales though.
Here's another contrived example. We have a certain material and want to know all the orders that used this material. Since we want orders, it makes sense to me to add this in the sales module. Again, you can perform joins to get the answer, and again this doesn't necessarily involve importing from other modules. Conceptually, though, it just doesn't feel right.